两个(未排序)数据表之间匹配的优化算法?

时间:2013-03-04 19:11:43

标签: c# .net algorithm

我正在处理我的应用程序中的一些功能,它们查询我们的数据库并将数据拉到一个数据表,然后打开一个excel文件并填充另一个数据表。

因为excel文件不包含可用的ID,所以我无法对数据进行排序,也可能无法使用DataTable.Merge()

以下是我创建的匹配算法的代码。

 private void RunMatchingAlgorithm()
    {
        // Initialize variables
        string partNumber = "";
        DateTime expiration_date = DateTime.Now;
        decimal contract_cost = 0;
        string contract_no = "";

        string partNumber2 = "";
        DateTime expiration_date2 = DateTime.Now;
        decimal contract_cost2 = 0;
        string contract_no2 = "";

        //Get values from DataBase
        for (int i = 0; i < dtFromTableContracts.Rows.Count; i++)
        {
            partNumber2 = dtFromTableContracts.Rows[i]["supplier_part_no"].ToString();
            contract_no2 = dtFromTableContracts.Rows[i]["contract_no"].
            expiration_date2 = Convert.ToDateTime(dtFromTableContracts.Rows[i]["con_end_date"]).Date;

            //Get Values from converted Excel table
            for (int j = 0; j < dtConversion.Rows.Count; j++)
            {
                contract_no = dtConversion.Rows[j]["vend_contract_no"].ToString();

                //If we have even a partial match, check for a part number match
                if (contract_no2.StartsWith(contract_no))
                {
                    partNumber = dtConversion.Rows[j]["vend_item_id"].ToString();

                    //If the values match, populate from both tables
                    if (partNumber == partNumber2)
                    {
                        dtConversion.Rows[j]["wpd_expiration_date"] = expiration_date2.Date;
                        dtConversion.Rows[j]["wpd_cont_cost"] = dtFromTableContracts.Rows[i]["contract_cost"];
                        dtConversion.Rows[j]["wpd_contract_no"] = dtFromTableContracts.Rows[i]["contract_no"];
                        dtConversion.Rows[j]["wpd_item_id"] = dtFromTableContracts.Rows[i]["supplier_part_no"];
                        dtConversion.Rows[j]["wpd_item_no"] = dtFromTableContracts.Rows[i]["item_id"];
                        dtConversion.Rows[j]["discontinued"] = dtFromTableContracts.Rows[i]["discontinued"];
                        dtConversion.Rows[j]["job_no"] = dtFromTableContracts.Rows[i]["job_no"];
                    }
                }
            }
        }
    }

如果您感到好奇,以后的方法会删除所有不匹配的行,我们只会在DGV中显示匹配的记录。

这当前按预期工作,但如果我的Big O符号是正确的,我正在处理O(m * n),这对于更大的数据集而言变得非常慢,而且处理器密集程度极高。

我正在寻找一种更有效的方法来实现这一点,而不是循环每一行,因为我们使用的一些excel电子表格接近40,000行。使用该集合的大小,此算法大约需要6分钟。

5 个答案:

答案 0 :(得分:3)

哦,小伙子,代码有很多简化的机会。您可以减少局部变量的范围,消除任何诱惑,为它们分配未使用的值。除了访问集合外,还可以在不使用索引时将For循环转换为ForEach循环。

初步简化:

private void RunMatchingAlgorithm() {
    foreach (var databaseRow in dtFromTableContracts.Rows) {
        string partNumber2 = databaseRow["supplier_part_no"].ToString();
        string contract_no2 = databaseRow["contract_no"].ToString();
        DateTime expiration_date2 = Convert.ToDateTime(databaseRow["con_end_date"]).Date;

        foreach (var excelRow in dtConversion.Rows) {
            string contract_no = excelRow["vend_contract_no"].ToString();

            //If we have even a partial match, check for a part number match
            if (contract_no2.StartsWith(contract_no)) {
                string partNumber = excelRow["vend_item_id"].ToString();

                //If the values match, populate from both tables
                if (partNumber == partNumber2) {
                    excelRow["wpd_expiration_date"] = expiration_date2.Date;
                    excelRow["wpd_cont_cost"] = databaseRow["contract_cost"];
                    excelRow["wpd_contract_no"] = databaseRow["contract_no"];
                    excelRow["wpd_item_id"] = databaseRow["supplier_part_no"];
                    excelRow["wpd_item_no"] = databaseRow["item_id"];
                    excelRow["discontinued"] = databaseRow["discontinued"];
                    excelRow["job_no"] = databaseRow["job_no"];
                }
            }
        }
    }
}

想想看,这几乎是linq查询的设计用例。我们可以将大部分代码转换为查询:

private void RunMatchingAlgorithm() {
    var matches = from databaseRow in dtFromTableContracts.Rows
                  let partNumber2 = databaseRow["supplier_part_no"].ToString()
                  let contract_no2 = databaseRow["contract_no"].ToString()
                  let expiration_date2 = Convert.ToDateTime(databaseRow["con_end_date"]).Date
                  from excelRow in dtConversion.Rows
                  let contract_no = excelRow["vend_contract_no"].ToString()
                  where contract_no2.StartsWith(contract_no)
                  let partNumber = excelRow["vend_item_id"].ToString()
                  where partNumber == partNumber2
                  select new { databaseRow, excelRow, expiration_date2 }
    foreach (var m in matches) {
        var dst = m.excelRow;
        var src = m.databaseRow;

        dst["wpd_expiration_date"] = m.expiration_date2.Date;
        dst["wpd_cont_cost"] = src["contract_cost"];
        dst["wpd_contract_no"] = src["contract_no"];
        dst["wpd_item_id"] = src["supplier_part_no"];
        dst["wpd_item_no"] = src["item_id"];
        dst["discontinued"] = src["discontinued"];
        dst["job_no"] = src["job_no"];
    }
}

现在我看到可以应用优化的位置。我们正在使用'where'进行嵌套'from',这相当于交叉连接。此外,我们可以削减大部分现在只使用过的临时演员:

private void RunMatchingAlgorithm() {
    var matches = from databaseRow in dtFromTableContracts.Rows
                  join excelRow in dtConversion.Rows
                  on excelRow["vend_item_id"].ToString() equals databaseRow["supplier_part_no"].ToString()
                  where databaseRow["contract_no"].ToString().StartsWith(excelRow["vend_contract_no"].ToString())
                  select new { databaseRow, excelRow }
    foreach (var m in matches) {
        var dst = m.excelRow;
        var src = m.databaseRow;

        dst["wpd_expiration_date"] = Convert.ToDateTime(src["con_end_date"]).Date;
        dst["wpd_cont_cost"] = src["contract_cost"];
        dst["wpd_contract_no"] = src["contract_no"];
        dst["wpd_item_id"] = src["supplier_part_no"];
        dst["wpd_item_no"] = src["item_id"];
        dst["discontinued"] = src["discontinued"];
        dst["job_no"] = src["job_no"];
    }
}

我实际上并没有多少使用交叉连接,但我认为它们使用了一个哈希表来获得O(n + m)复杂度而不是O(n * m)。如果两个表都在数据库中,那么数据库可以利用已经构造的哈希表/索引。

您可能还想考虑某种生成的Linq2SQL类,因此您可以对行字段进行类型安全访问。

答案 1 :(得分:0)

您可以散列要匹配的值并具有O(m + n)复杂度。

你必须创建一个解析双方并创建一对(或一组)统一结果然后可以进行哈希处理的函数。例如,如果您的contract_no一侧有一些已知格式的前缀而另一侧没有前缀,则可以删除前缀和散列。

答案 2 :(得分:0)

在找到匹配的部件号后,您应该能够通过退出内循环将时间缩短一半。也就是说,将break作为部件号匹配时执行的代码中的最后一个语句。

但是,永远的一半仍然永远。

只有40,000行,您可以轻松地填充包含合同号和部件号作为键的字典,并将转换表行索引作为值。类似的东西:

Dictionary<string, int> conversionLookup = new Dictionary<string, int>();
for (int i = 0; i < dtConversion.Rows.Count; ++j)
{
    conversionLookup.Add(string.Format("{0}:{1}", 
        dtConversion.Rows[j]["vend_contract_no"].ToString(),
        dtConversion.Rows[j]["vend_item_id"].ToString()), j);
}

然后,你的外环变为:

for (int i = 0; i < dtFromTableContracts.Rows.Count; i++)
{
    partNumber2 = dtFromTableContracts.Rows[i]["supplier_part_no"].ToString();
    contract_no2 = dtFromTableContracts.Rows[i]["contract_no"].ToString();
    string lookup = string.Format("{0}:{1}", contract_no2, partNumber2);
    int ix;
    if (conversionLookup(lookup, out ix))
    {
        // update dtConversion.Rows[ix]
    }
}

我不熟悉您的部件号码的限制(在内循环中使用StartsWith而不是等于),因此您可能需要稍微调整一下索引。

那应该非常快。

如果您无法创建使用合同编号进行直接查找的密钥,则可以通过创建包含合同编号和部件编号的List以及二进制搜索来完成类似的操作。那将是O(m log n)而不是O(m * n)。还是要快一点。

答案 3 :(得分:0)

Trie是查找子字符串的最快结构。

答案 4 :(得分:0)

如果ContractTable.ContractNo具有已知的常量长度,那么您在(事实上)两个表之间存在PK-FK关系:

ContractTable.ContractNo = substring(Conversion.VendContractNo,1,K)
ContractTable.PartNumber = Conversion.PartNumber

其中K == Length(ContractTabel.ContractNo)。

对此密钥结构上的两个表进行索引将允许在O(Log N)+ O(Log M)时间内进行匹配,索引创建为O(N * Log N)+ O(M * Log M)时间。 Hashing可以进一步改善这一点,具体取决于良好哈希的构造。