【问题标题】:Numeric Range Optimization数值范围优化
【发布时间】:2011-07-30 20:54:35
【问题描述】:

我有一组想要优化的数值范围。

这是一个简单的初始值示例:

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

优化后我期望的输出:

Start    End
1        2
9        12
60       88

这些是存储在 MySQL 数据库中的修改后的先序树遍历(嵌套集)数据中的 leftright 值。我使用它们从结果中排除不活动的分支,并且目前根本没有优化范围。我想我可能会通过在使用前优化范围来获得性能提升。


更多信息

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

【问题讨论】:

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


【解决方案1】:

将它们放在一个排序列表中。标记排序列表中的哪些元素代表范围开始,哪些是范围结束。首先根据值对列表进行排序;但是,请确保范围开始于范围结束之前。 (这可能会涉及某种可以按给定键排序的结构。我不知道 php 中的详细信息。)

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

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

编辑:: 如果您已经在某处的数据库表中有范围,您可能可以构造一个 SQL 查询来执行上述第一步(再次确保返回范围起点在范围端点之前)。

【讨论】:

  • 将范围开始放入一个列表,范围结束到另一个列表也足够了;然后你对它们都进行迭代,有两个指针并推进指向较小数字的指针。这样,关于范围开始和结束的相对顺序的问题也消失了,在 SQL 中应该更容易做到(进行两个查询,一个用于范围开始,一个用于范围结束,然后在 SQL 查询中对它们进行排序本身)
  • 这种方法适用于寻找范围联合的一般问题。您应该注意,OP 专门要求来自“嵌套集”的范围 - 其中范围要么是适当的子集,要么没有交集(因此是嵌套的)。
【解决方案2】:

这是一个简单的实现:

// 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_filter 提供适当的回调:function($item) { return $item === null; }

See it in action.

【讨论】:

    【解决方案3】:
    $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
    

    【讨论】:

      【解决方案4】:

      这是一个返回你想要的东西的 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 primary/dependent subquery for EXISTS),因此性能可能会有所不同。如果存在,两个查询都将使用 (Start, End) 上的索引。

      【讨论】:

      • 这很好用!我现在注意到我也想合并相邻的节点。 1,23,4 将变为 1,4
      • @Sonny,好的 - 这是一个不同的问题,对于这个问题,其他提出的解决方案是很好的算法。但是,我有点困惑,你的目的是什么?在一般使用场景中优化嵌套集是不必要的 - 它已经是一种优化的数据结构,可以利用数据库索引进行其他递归查询。
      • 我想我会在有机会的时候创建一个新帖子。我可能处理错了,或者我当前的解决方案可能是最佳的。
      猜你喜欢
      • 2015-01-15
      • 1970-01-01
      • 2014-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多