数值范围优化

时间:2011-04-06 14:03:29

标签: php mysql algorithm nested-sets modified-preorder-tree-t

我有一个我想优化的设定数值范围。

以下是初始值的简单示例:

Start    End
9        12
1        2
60       88
10       11
79       80

优化后我期望输出的结果:

Start    End
1        2
9        12
60       88

这些是存储在MySQL数据库中的Modified Preorder Tree Traversal(嵌套集)数据的leftright值。我使用它们从结果中排除非活动分支,并且目前根本没有优化范围。我认为在使用前优化范围可能会带来性能提升。


更多信息

将值传递到查询中,以使用NOT BETWEEN子句排除树中的非活动分支。我认为我可以通过使用一组最小范围来优化该查询的性能。

4 个答案:

答案 0 :(得分:2)

将它们放入已排序的列表中。标记排序列表中的哪些元素表示范围开始,哪些是范围结束。首先根据值对列表进行排序;但是,请确保范围在范围结束之前开始。 (这可能涉及一种可以在给定密钥上排序的某种结构。我不知道php中的细节。)

现在,从头到尾遍历列表。保留一个柜台,c。当您通过范围开始时,请递增c。传递范围结束时,递减c

c从0变为1时,这是最终集合中范围的开始。当c从1变为0时,那就是范围的结束。

编辑:: 如果你已经在某个数据库表中有了范围,你可以构建一个SQL查询来执行上面的第一步(同样,确保返回范围起始点)在范围终点之前)。

答案 1 :(得分:2)

这是一个将返回你想要的SQL

mysql> CREATE TABLE sample (Start INT, End INT);

mysql> INSERT sample VALUES (9,12),(1,2),(60,88),(10,11),(79,80);

mysql> SELECT * 
    -> FROM sample s 
    -> WHERE NOT EXISTS (SELECT 1 
    ->                   FROM sample 
    ->                   WHERE s.Start > Start AND s.Start < End);
+-------+------+
| Start | End  |
+-------+------+
|     9 |   12 |
|     1 |    2 |
|    60 |   88 |
+-------+------+

当然,您可以使用上述SQL创建VIEW,将数据移动到另一个表或删除行。

注意:我不确定你为什么要做这个'优化'。

编辑:
查询可以重写为

SELECT s.* 
FROM sample s LEFT JOIN 
     sample s2 ON s.Start > s2.Start AND s.Start < s2.End 
WHERE s2.start IS NULL;

这将创建不同的执行计划(2xsimple select vs EXISTS的主要/从属子查询),因此性能可能会有所不同。如果存在,两个查询都将使用(开始,结束)索引。

答案 2 :(得分:0)

这是一个简单的实现:

// I picked this format because it's convenient for the solution
// and because it's very natural for a human to read/write
$ranges = array(
  9    =>    12,
  1    =>    2,
  60   =>    81,
  10   =>    11,
  79   =>    88);

ksort($ranges);
$count = count($ranges);
$prev = null; // holds the previous start-end pair

foreach($ranges as $start => $end) {
    // If this range overlaps or is adjacent to the previous one
    if ($prev !== null && $start <= $prev[1] + 1) {
        // Update the previous one (both in $prev and in $ranges)
        // to the union of its previous value and the current range
        $ranges[$prev[0]] = $prev[1] = max($end, $prev[1]);

        // Mark the current range as "deleted"
        $ranges[$start] = null;
        continue;
    }

    $prev = array($start, $end);
}

// Filter all "deleted" ranges out
$ranges = array_filter($ranges);

限制/注释:

  1. 范围边界必须足够小以适合int
  2. 如果结束边界为0,此示例将错误地从最终结果中删除任何范围。如果您的数据可以合法地包含此类范围,请提供适当的回调array_filterfunction($item) { return $item === null; }
  3. <强> See it in action

答案 3 :(得分:0)

$ranges = array(
  array(9, 12),
  array(1, 2),
  array(60, 81),
  array(10, 11),
  array(79, 88),
  );

function optimizeRangeArray($r) {
  $flagarr = array();
  foreach ($r as $range => $bounds) {
    $flagarr = array_pad($flagarr, $bounds[1], false);
    for ($i = $bounds[0]-1; $i < $bounds[1]; $i++) $flagarr[$i] = true;
    }
  $res = array(); $min = 0; $max = 0; $laststate = false;
  $ctr = 0;
  foreach ($flagarr as $state) {
    if ($state != $laststate) {
      if ($state) $min = $ctr + 1;
      else {
        $max = $ctr;
        $res[] = array($min, $max);
        }
      $laststate = $state;
      }
    $ctr++;
    }
  $max = $ctr;
  $res[] = array($min, $max);
  return($res);
  }

print_r(optimizeRangeArray($ranges));

输出:

Array
(
    [0] => Array
        (
            [0] => 1
            [1] => 2
        )

    [1] => Array
        (
            [0] => 9
            [1] => 12
        )

    [2] => Array
        (
            [0] => 60
            [1] => 88
        )

)

注意:这不适用于负整数!

或者像这样使用

$rres = optimizeRangeArray($ranges);

$out = "<pre>Start    End<br />";
foreach($rres as $range=>$bounds) {
  $out .= str_pad($bounds[0], 9, ' ') . str_pad($bounds[1], 9, ' ') . "<br />";
  }
$out .= "</pre>";
echo $out;

要在浏览器中显示

Start    End
1        2
9        12
60       88