查找符合范围标准的组合

时间:2012-02-17 20:48:40

标签: php javascript mysql

我有一个包含1000行的零件清单。这两个字段是“部件号”和“成本”。费用从1美元到2000美元不等(全部为整数)。

  • A034,1
  • A012,5
  • A084,10
  • B309,13
  • A094,25
  • A370,50
  • A233,75
  • A343,75
  • C124,78
  • ...
  • D239,500
  • ...
  • X998,1980
  • Z901,2000

我想创建一个列表,其中包含组合成本在一个较窄范围内的所有零件组合(范围的差距永远不会超过50美元)。例如,给定70-75美元的范围,返回的列表将是:

  • A343(总计75美元)
  • A233(总计75美元)
  • A370,A094(总计75美元)
  • A370,B309,A084(总计$ 73)
  • A370,B309,A084,A034(总计74美元)

我的第一个想法是迭代可以满足标准的所有可能的部分组合(即< =范围的上限数)并报告其总和落在该范围内的那些组合。很快就会明显失败,因为组合的数量变得非常快。但鉴于大多数组合不符合标准,这个问题是否可以合理解决?

鉴于数据位于MySQL数据库的表中,我首选的解决方案是SQL或Stored Proc,下一个首选PHP,最后是Javascript。

(ETA:@piotrm发现的漏洞)

3 个答案:

答案 0 :(得分:3)

您必须限制总费用的最大值,或者无论您如何尝试找到它们,组合的数量都将上升到天空。在以下示例中,它仅限于75,但您可以尝试其他值来查看它,您仍然可以在合理的时间内找到结果。

您还可以调整此解决方案以更新主表的插入或更新组合表,让您可以非常快速地获得不超出设定限制的任何范围的结果(但显然减慢了插入,因为它完成了所有工作)。

创建表格和触发器:

CREATE TABLE `total_max75` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `parts` varchar(255) NOT NULL,
 `num` int(11) NOT NULL,
 `total` int(11) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `total` (`total`,`num`)
); 

CREATE TABLE `newparts` (
 `name` char(4) NOT NULL,
 `price` int(11) NOT NULL,
 PRIMARY KEY (`name`)
);

DELIMITER //
CREATE TRIGGER addtotal AFTER INSERT ON newparts
FOR EACH ROW
BEGIN
IF NEW.price <= 75 THEN
   INSERT INTO total_max75 ( parts, num, total )
     SELECT CONCAT( t.parts, ', ', NEW.name), 
       t.num+1, t.total+NEW.price 
   FROM total_max75 t
   WHERE t.total <= 75 - NEW.price AND num < 40;

   INSERT INTO total_max75( parts, num, total )
     VALUES( NEW.name, 1, NEW.price );
END IF;
END//
DELIMITER ;

然后使用:

填充
INSERT INTO newparts( name, price )
SELECT part_number, cost FROM yourtable
WHERE cost <= 75;

或(作为测试数据)

INSERT INTO newparts( name, price ) VALUES
('A012', 5),('A034', 1),('A084', 10),('A094', 25),('A233', 75),
('A343', 75),('A370', 50),('B309', 13),('C124', 78);

最后使用:

获得结果
SELECT * FROM total_max75 WHERE total BETWEEN 70 AND 75;

您可以在此处放置任何范围,最大值小于75(或者您在表创建部分和触发器中设置为限制的任何范围)。

结果:

A084, A370, B309        73 (you missed this one in your question)
A034, A084, A370, B309  74
A233                    75
A343                    75
A094, A370              75

答案 1 :(得分:2)

好吧,我的第一个想法是只自动加入表中成本低于范围高端的行:

select 
  l.part as `part1`, r.part as `part2`, l.cost + r.cost as `total cost`
from (select * from parts where cost < MAX_COST) l
inner join
(select * from parts where cost < MAX_COST) r
on l.cost + r.cost between MIN_COST and MAX_COST

这有多高效?

  1. 行数小于MAX_COST 的O(n ^ 2)。如果该数字不是很小,这可能会很慢
  2. parts行数中的O(n)。如果parts非常大,这可能是决定因素。但如果parts不是那么大,或者如果(1)不小,这将被(1)
  3. 淹没

答案 2 :(得分:1)

我有一个递归的(在数组上):

$data = array( 
  'A034' => 1,
  'A012' => 5,
  'A084' => 10,
  ...
)

我们需要先使用arsort()对数据进行排序,并使用最高值:

arsort( $data, SORT_NUMERIC);

这将确保在早期阶段处理大部分阵列。关于主要功能:

/**
 * Builds resulting array
 *
 * @param $array $key => $price pairs
 * @param $price lower price of interval (for <70,130>, it's 70)
 * @param $reserve difference between lower and higher price (for <70,130>, it's 130-70 = 59)
 * @param &$result output array
 * @param $cummulatedKeys so far collected keys, leave empty
 *
 * @return void
 */
function buildResults( $array, $price, $reserve, &$result, $cumulatedKeys = array()){
    // Get key of first element
    reset( $array);
    $key = key( $array);

    // Just decrease number of elements as fast as possible
    while( $one = array_shift( $array)){
       $tmp = $price - $one;

       // Ok reached one price
       if( $tmp >= 0){
           // In interval
           if( (-$tmp) <= $reserve){
               $result[] = array_merge( $cumulatedKeys, array( $key));
           } else {
                 // We are too low
                 continue;
           }
       }

       // Skip very small values which can't accumulate price
       if( (count( $array) * $one) < $tmp){
           break;
       }

       // We may go on, deeper
       buildResults( $array, $tmp, $reserve, $result, array_merge( $cumulatedKeys, array( $key)));

       // Actualize key
       if( !count( $array)){
           break;
       }
       reset( $array);
       $key = key( $array);
   }
}

用法应该是显而易见的,但仅针对这种情况,假设您希望处理$array值为区间<70,90>;

$result = array();
buildResults( $array, 70, 90-70, $result);

我没有测试过,我对它的表现感到好奇......请在评论中留言