在PHP中删除间隔数组中的封闭间隔

时间:2016-08-02 18:09:06

标签: php arrays intervals

我有一个按下限排序的间隔数组(每个$ i $ $ [$ i]< = $ a [$ i + 1]),键 l 更低bound和,key h 是上限,我想删除所有行,这些行的间隔由较大的间隔包围。

$a[0] = array('l' => 123, 'h'=>241);
$a[1] = array('l' => 250, 'h'=>360);
$a[2] = array('l' => 280, 'h'=>285);
$a[3] = array('l' => 310, 'h'=>310);
$a[4] = array('l' => 390, 'h'=>400);

所以我想得到的结果是

$a[0] = array('l' => 123, 'h'=>241);
$a[1] = array('l' => 250, 'h'=>360);
$a[2] = array('l' => 390, 'h'=>400);

这就是我的尝试

function dup($a){
  $c = count($a)-1;
  for ($i = $c; $i > 0; $i --){
    while ($a[$i]['h'] <= $a[$i-1]['h']){
        unset($a[$i]);
    }
  }
$a = array_values($a);
}

5 个答案:

答案 0 :(得分:1)

第一个回答的问题是由其他贡献者给出的不同变化:对于每个区间,每个区间的循环寻找更大的封闭区间。它理解和编写简单,而且确实有效。

这基本上是n2阶,这意味着对于n个间隔,我们将进行n * n次循环。可以有一些技巧来优化它:

    当我们在嵌套循环中找到一个封闭区间时,
  • 中断,如在user3137702的答案中,因为如果我们找到至少一个封闭区间,它就无法继续
  • 避免在嵌套循环中的相同区间循环,因为我们知道区间不能严格地包含在其中(不重要)
  • 避免在嵌套循环中已经排除的时间间隔上循环(可能会产生重大影响)
  • 以上升宽度=(h - l)顺序循环间隔(全局循环),因为较小的间隔有更多机会被包含在其他区域中,并且最早我们消除间隔,下一个循环转动有效的越多(可以是在我看来也很重要)
  • 以降序宽度顺序搜索封闭间隔(嵌套循环),因为较大的间隔有更多机会封闭其他间隔(我认为它也会产生重大影响)
  • 可能还有许多其他目前无法想到的事情

现在让我说:

  • 优化并不重要,如果我们不时只有很少的时间间隔来计算,并且当前接受的用户3137702的答案就是诀窍
  • 开发合适的算法,无论如何都要研究我们必须处理的数据的特征:在我们之前的情况下,如何分配间隔?是否有许多封闭间隔?这有助于从上面的列表中选择最有用的技巧。

出于教育目的,我想知道我们是否可以开发一种避免n * n顺序的不同算法,随着你增加计算间隔的数量,运行时间必然会逐渐恶化。

&#34;虚拟规则&#34;算法

我想象这个算法我称之为&#34;虚拟规则&#34;。

  • 在虚拟规则上放置间隔的起点和终点
  • 按升序顺序浏览规则
  • 在运行期间,注册打开或不打开间隔
  • 当间隔开始和结束而另一个间隔开放并且仍然打开时,我们可以说它是封闭的
  • 所以当一个间隔结束时,检查它是否在其他一个当前打开的间隔之后打开,并且在此间隔之前是否严格关闭。如果是的话,它是随附的!

我不假装这是最好的解决方案。但我们可以假设这比基本方法更快,因为尽管在循环期间要做很多测试,但这是n阶。

代码示例

我写了评论以尽可能清楚。

<?php
    function removeEnclosedIntervals_VirtualRule($a, $debug = false)
    {
        $rule = array();

        // place one point on a virtual rule for each low or up bound, refering to the interval's index in $a
        // virtual rule has 2 levels because there can be more than one point for a value
        foreach($a as $i => $interval)
        {
            $rule[$interval['l']][] = array('l', $i);
            $rule[$interval['h']][] = array('h', $i);
        }

        // used in the foreach loop
        $open = array();
        $enclosed = array();

        // loop through the points on the ordered virtual rule
        ksort($rule);
        foreach($rule as $points)
        {
            // Will register open intervals
            // When an interval starts and ends while another was opened before and is still open, it is enclosed

            // starts
            foreach($points as $point)
                if($point[0] == 'l')
                    $open[$point[1]] = $point[1]; // register it as open

            // ends
            foreach($points as $point)
            {
                if($point[0] == 'h')
                {
                    unset($open[$point[1]]); // UNregister it as open

                    // was it opened after a still open interval ?
                    foreach($open as $i)
                    {
                        if($a[$i]['l'] < $a[$point[1]]['l'])
                        {
                            // it is enclosed.

                            // is it *strictly* enclosed ?
                            if($a[$i]['h'] > $a[$point[1]]['h'])
                            {
                                // so this interval is strictly enclosed
                                $enclosed[$point[1]] = $point[1];

                                if($debug)
                                    echo debugPhrase(
                                        $point[1], //           $iEnclosed
                                        $a[$point[1]]['l'], //  $lEnclosed
                                        $a[$point[1]]['h'], //  $hEnclosed

                                        $i, //                  $iLarger
                                        $a[$i]['l'], //         $lLarger
                                        $a[$i]['h'] //          $hLarger
                                    );

                                break;
                            }
                        }
                    }
                }
            }
        }

        // obviously
        foreach($enclosed as $i)
            unset($a[$i]);

        return $a;
    }
?>

对基本方法进行基准测试

  • 它以随机生成的间隔运行测试
  • 基本方法毫无疑问有效。比较两种方法的结果使我能够预先确定&#34; VirtualRule&#34;方法有效,因为据我测试,它返回相同的结果

    // * include removeEnclosingIntervals_VirtualRule function *
    
    // arbitrary range for intervals start and end
    // Note that it could be interesting to do benchmarking with different MIN and MAX values !
    define('MIN', 0);
    define('MAX', 500);
    
    // Benchmarking params
    define('TEST_MAX_NUMBER', 100000);
    define('TEST_BY_STEPS_OF', 100);
    
    // from http://php.net/manual/en/function.microtime.php
    // used later for benchmarking purpose
    function microtime_float()
    {
        list($usec, $sec) = explode(" ", microtime());
        return ((float)$usec + (float)$sec);
    }
    
    function debugPhrase($iEnclosed, $lEnclosed, $hEnclosed, $iLarger, $lLarger, $hLarger)
    {
        return '('.$iEnclosed.')['.$lEnclosed.' ; '.$hEnclosed.'] is strictly enclosed at least in ('.$iLarger.')['.$lLarger.' ; '.$hLarger.']'.PHP_EOL;
    }
    
    // 2 foreach loops solution (based on user3137702's *damn good* work ;) and currently accepted answer)
    function removeEnclosedIntervals_Basic($a, $debug = false)
    {
        foreach ($a as $i => $valA)
        {
            $found = false;
    
            foreach ($a as $j => $valB)
            {
                if (($valA['l'] > $valB['l']) && ($valA['h'] < $valB['h']))
                {
                    $found = true;
    
                    if($debug)
                        echo debugPhrase(
                            $i, //                  $iEnclosed
                            $a[$i]['l'], //         $lEnclosed
                            $a[$i]['h'], //         $hEnclosed
    
                            $j, //                  $iLarger
                            $a[$j]['l'], //         $lLarger
                            $a[$j]['h'] //          $hLarger
                        );
    
                    break;
                }
            }
    
            if (!$found)
            {
                $out[$i] = $valA;
            }
        }
    
        return $out;
    }
    
    // runs a benchmark with $number intervals
    function runTest($number)
    {
        // Generating a random set of intervals with values between MIN and MAX
        $randomSet = array();
        for($i=0; $i<$number; $i++)
            // avoiding self-closing intervals
            $randomSet[] = array(
                'l' => ($l = mt_rand(MIN, MAX-2)),
                'h' => mt_rand($l+1, MAX)
            );
    
        /* running the two methods and comparing results and execution time */
    
        // Basic method
        $start = microtime_float();
        $Basic_result = removeEnclosedIntervals_Basic($randomSet);
        $end = microtime_float();
        $Basic_time = $end - $start;
    
        // VirtualRule
        $start = microtime_float();
        $VirtualRule_result = removeEnclosedIntervals_VirtualRule($randomSet);
        $end = microtime_float();
        $VirtualRule_time = $end - $start;
    
        // Basic method works for sure.
        // If results are the same, comparing execution time. If not, sh*t happened !
        if(md5(var_export($VirtualRule_result, true)) == md5(var_export($VirtualRule_result, true)))
            echo $number.';'.$Basic_time.';'.$VirtualRule_time.PHP_EOL;
        else
        {
            echo '/;/;/;Work harder, results are not the same ! Cant say anything !'.PHP_EOL;
            stop;
        }
    }
    
    // CSV header
    echo 'Number of intervals;Basic method exec time (s);VirtualRule method exec time (s)'.PHP_EOL;
    
    for($n=TEST_BY_STEPS_OF; $n<TEST_MAX_NUMBER; $n+=TEST_BY_STEPS_OF)
    {
        runTest($n);
        flush();
    }
    

结果(对我而言)

  • 我认为,显然可以获得不同的表现。
  • 我在使用PHP5的Core i7计算机和使用PHP7的(旧)AMD四核计算机上运行测试。我的系统上的两个版本在性能上存在明显差异!原则上可以通过PHP版本的差异来解释,因为运行PHP5的计算机功能更强大......

PHP5

PHP7

答案 1 :(得分:0)

一种简单的方法,可能不是你想要的,但至少应该指向正确的方向。我可以根据需要改进它,只是有点忙,并且不想让这个问题没有答案。

$out = [];

foreach ($a as $valA)
{
  $found = false;

  foreach ($a as $valB)
  {
    if (($valA['l'] > $valB['l']) && ($valA['h'] < $valB['h']))
    {
      $found = true;
      break;
    }
  }

  if (!$found)
  {
    $out[] = $valA;
  }
}

这是完全未经测试的,但最终只能以$ out中的唯一(大)范围结束。我在评论中提到的重叠是未处理的。

答案 2 :(得分:0)

问题是在while循环中缺少中断

function dup($a){
  $c = count($a)-1;
  for ($i = $c; $i > 0; $i --){
     while ($a[$i]['h'] <= $a[$i-1]['h']){
      unset($a[$i]);
      break; //here
     }
  }
  $a = array_values($a);
}

答案 3 :(得分:0)

这是代码

function sort_by_low($item1,$item2){
    if($item1['l'] == $item2['l'])
        return 0;
    return ($item1['l']>$item2['l'])? -1:1;
}

usort($a,'sort_by_low');

for($i=0; $i<count($a); $i++){
  for($j=$i+1; $j<count($a);$j++){
     if($a[$i][l]<=$a[$j]['l'] && $a[$i][h]>=$a[$j]['h']){
        unset($a[$j]);
     }
  }
}

$a=array_values($a);

答案 4 :(得分:0)

这是工作代码(已测试)

CREATE TABLE test (
     StartOfWeek DATETIME,
     Total INT
    )
INSERT  INTO test
VALUES  ('1/17/2016',8),
        ('1/24/2016',8),
        ('1/31/2016',10),
        ('2/7/2016',10),
        ('2/14/2016',14),
        ('2/21/2016',10),
        ('2/28/2016',10)

;WITH cte AS (
    SELECT  *,
            ROW_NUMBER()  OVER (ORDER BY StartOfWeek) Rn
    FROM    test
)
SELECT  c1.StartOfWeek,
        c1.Total,
        -- incremement by 1 if Total value changes
        SUM(CASE WHEN c1.Total= c2.Total THEN 0 
                 ELSE 1 END) OVER (ORDER BY c1.Rn) AS RowNumber
FROM    cte c1
        LEFT JOIN cte c2 ON c1.Rn = c2.Rn + 1

Result

StartOfWeek             Total       RowNumber
----------------------- ----------- -----------
2016-01-17 00:00:00.000 8           1
2016-01-24 00:00:00.000 8           1
2016-01-31 00:00:00.000 10          2
2016-02-07 00:00:00.000 10          2
2016-02-14 00:00:00.000 14          3
2016-02-21 00:00:00.000 10          4
2016-02-28 00:00:00.000 10          4

$result = array(); usort($a, function ($item1, $item2) { if ($item1['l'] == $item2['l']) return 0; return $item1['l'] < $item2['l'] ? -1 : 1; }); foreach ($a as $element) { $exists = false; foreach ($result as $r) { if (($r['l'] < $element['l'] && $r['h'] > $element['h'])) { $exists = true; break; } } if (!$exists) { $result[] = $element; } } 将包含所需的结果