PHP:在数据库中查找一组总和特定数字的数字

时间:2017-03-05 15:56:39

标签: php algorithm dynamic-programming memoization knapsack-problem

首先,我是一个php新手...所以我仍然在程序上编码和理解php。那说,

我有一组存储在数据库中的数字(金额)。

问题:使用PHP和mySQL,

  1. 什么是将此信息从数据库中取出的最佳方法,以便金额与其交易ID相关联

  2. 最重要的是,我需要在数据库中找到一组匹配的数字,它们等于 29 的总和。

  3. 以下是我的数据库Transaction_tlb

    的交易表mydb
        Transaction_ID |     Name         |       Date      | Amount
        ---------------|------------------|-----------------|------------ 
        11012          | Jonathan May     |   6/12/2016     |     84
        21012          | John Pedesta     |   6/12/2016     |     38
        31012          | Mary Johnson     |   1/01/2017     |     12
        41012          | John Johnson     |   8/01/2017     |     13
        51012          | Keith Jayron     |   8/01/2017     |     17
        61012          | Brenda Goldson   |   8/01/2017     |     2
        71012          | Joshua Traveen   |   8/01/2017     |     78
        81012          | Remy ma Goldstein|   8/01/2017     |     1
        91012          | Barbie Traveen   |   8/01/2017     |     1
    

    现在,我有一个想法..但它效率不高。我将尝试每一种可能的情况。意思是如果我有n个要检查的值,时间复杂度将约为2 ^ n。这是非常低效的(另外,我甚至不知道我的代码是否有意义。(见下文

    我在此YouTube视频中看到了类似的示例:https://www.youtube.com/watch?v=XKu_SEDAykw&t

    但是,我不确定如何在php中编写代码。

    代码:

    <?php
      if (!mysql_connect("localhost", "mysql_user", "mysql_password") || !mysql_select_db("mydb")) {
          die("Could not connect: " . mysql_error()); } //End DB Connect
    
      $capacity = 29; //Knapsack Capacity or Sum
    
      //Select Transact ID and Value from the Database where Amount is <= Capacity
      $fetchQuery = "SELECT 'Transaction_ID', 'Amount' FROM 'Transaction_tlb' WHERE 'Amount' <= $capacity"; 
    
      $components = array(); //new array to hold components
    
      if ($queryResults = mysql_query($fetchQuery)) {
    
         //check if data was pulled
         if (mysql_num_row($queryResults) != NULL) {
            while ($row = mysqli_fetch_assoc($queryResults) {
               $components[$row['Transaction_ID']] = $row['Amount'];
            }
         }
      }
    
      /* Correct me if i am wrong, but, Components associative array Should be something like
      $components = array('11012'=> 84, '21012'=> 38, '31012'=> 12, '41012'=> 13, '51012'=> 17, 
                          '61012'=> 2, '71012'=> 78, '81012'=> 1, '91012'=> 1);
      */
    
      $components = asort($components) // sort array in ascending order
      $componentCount = count($component)
    
    
      function match ($componentCount, $capacity) {
                  $temp = match (($componentCount - 1), $capacity);
                  $temp1 = $component[$componentCount] + match (($componentCount - 1), ($capacity - $component[$componentCount]));
                  $result = max($temp, $temp1);
             return $result;
             }
    }?>
    

    任何人都可以指出我正确的方向吗?这段代码不起作用......即使它有效......该方法根本没有效率。当我有300万条记录可以使用时会发生什么?我需要帮助。

2 个答案:

答案 0 :(得分:3)

您可以根据0/1 Knapsack problem制定问题。 PHP中的即用型实现是available

使用链接页面中定义的函数knapSolveFast2,可以按照下面的示例继续操作。这里的想法是你设置进入Knapsack算法的“权重”等于值本身。

$components = array(84, 38, 12, 13, 17, 2, 78, 1, 1);

$m = array();
list($m4, $pickedItems) = knapSolveFast2($components, $components, sizeof($components)-1, 29, $m);

echo "sum: $m4\n";
echo "selected components:\n";
foreach($pickedItems as $idx){
    echo "\t$idx --> $components[$idx]\n";
}

产生:

sum: 29
selected components:
    2 --> 12
    4 --> 17 

注意:

  • 您可以修改SQL查询,以便跳过amount大于所需总和的行(29)
  • 上面的函数将选择一个解决方案(假设它存在),它不会提供所有这些
  • 应该检查返回值$m4是否确实等于指定的总和(29) - 因为算法有效,指定的数量只是不能保证达到的上限(例如37而不是29,返回值只有34,因为没有输入数字的组合,其总和将产生37)

答案 1 :(得分:1)

这实际上是一个背包问题,但我会尝试提供一个不是最佳的完整解决方案,但会说明解决问题的完整策略。

首先,你只需对数字数组进行一次迭代就可以做到这一点,没有递归,也不需要预先排序。动态编程就是您所需要的,跟踪所有以前可能的部分和路径&#39;。这个想法有点类似于你描述的递归方法,但是我们可以迭代地进行,而无需预先分类。

假设输入数组为[84, 38, 12, 13, 17, 2, 78, 1, 1]且目标为29,我们就像这样循环数字:

* 84 - too big, move on
* 38 - too big, move on
* 12 - gives us a subtarget of 29-12 = 17
            subtargets:
              17 (paths: 12)
* 13 - gives us a subtarget of 29-13=16
            subtargets:
              16 (paths: 13)
              17 (paths: 12)
* 17 - is a subtarget, fulfilling the '12' path;
   and gives us a subtarget of 29-17=12
            subtargets:
              12 (paths: 17)
              16 (paths: 13)
              17 (paths: 12)
            solutions:
              12+17
etc.

这里的诀窍是,在循环数字的同时,我们保留一个subTargets的查找表,这些数字可以为我们提供使用一个或多个组合的解决方案(&#39;路径&#39;以前看过的数字。如果新数字是子目标,我们将添加到我们的解决方案列表中;如果没有,那么我们会附加到num<subTarget的现有路径并继续前进。

快速而又脏的PHP函数:

// Note: only positive non-zero integer values are supported
// Also, we may return duplicate addend sets where the only difference is the order
function findAddends($components, $target)
{
    // A structure to hold our partial result paths
    // The integer key is the sub-target and the value is an array of string representations
    // of the 'paths' to get to that sub-target. E.g. for target=29
    // subTargets = {
    //   26: { '=3':true },
    //   15: { '=12+2':true, '=13+1':true }
    // }
    // We are (mis)using associative arrays as HashSets
    $subTargets = array();

    // And our found solutions, stored as string keys to avoid duplicates (again using associative array as a HashSet)
    $solutions = array();

    // One loop to Rule Them All
    echo 'Looping over the array of values...' . PHP_EOL;
    foreach ($components as $num) {
        echo 'Processing number ' . $num . '...' . PHP_EOL;

        if ($num > $target) {
            echo $num . ' is too large, so we skip it' . PHP_EOL;
            continue;
        }

        if ($num == $target) {
            echo $num . ' is an exact match. Adding to solutions..' . PHP_EOL;
            $solutions['='.$num] = true;
            continue;
        }

        // For every subtarget that is larger than $num we get a new 'sub-subtarget' as well
        foreach ($subTargets as $subTarget => $paths) {
            if ($num > $subTarget) { continue; }

            if ($num == $subTarget) {
                echo 'Solution(s) found for ' . $num . ' with previous sub-target. Adding to solutions..' . PHP_EOL;
                foreach ($paths as $path => $bool) {
                    $solutions[$path . '+' . $num] = true;
                }
                continue;
            }

            // Our new 'sub-sub-target' is:
            $subRemainder = $subTarget-$num;
            // Add the new sub-sub-target including the 'path' of addends to get there
            if ( ! isset($subTargets[$subRemainder])) { $subTargets[$subRemainder] = array(); }

            // For each path to the original sub-target, we add the $num which creates a new path to the subRemainder
            foreach ($paths as $path => $bool) {
                $subTargets[$subRemainder][$path.'+'.$num] = true;
            }
        }

        // Subtracting the number from our original target gives us a new sub-target
        $remainder = $target - $num;

        // Add the new sub-target including the 'path' of addends to get there
        if ( ! isset($subTargets[$remainder])) { $subTargets[$remainder] = array(); }
        $subTargets[$remainder]['='.$num] = true;
    }
    return $solutions;
}

运行代码如下:

$componentArr = array(84, 38, 12, 13, 17, 2, 78, 1, 1);
$addends = findAddends($componentArr, 29);

echo 'Result:'.PHP_EOL;
foreach ($addends as $addendSet => $bool) {
    echo $addendSet . PHP_EOL;
}

输出:

Looping over the array of values...
Processing number 84...
84 is too large, so we skip it
Processing number 38...
38 is too large, so we skip it
Processing number 12...
Processing number 13...
Processing number 17...
Solution(s) found for 17 with previous sub-target. Adding to solutions..
Processing number 2...
Processing number 78...
78 is too large, so we skip it
Processing number 1...
Processing number 1...
Solution(s) found for 1 with previous sub-target. Adding to solutions..

Result:
=12+17
=12+13+2+1+1