get extremes from list of numbers [closed]

半城伤御伤魂 提交于 2019-12-02 13:45:47
ShadowScripter

Mine's similar to jprofitt's

but I divided them into peaks and valleys, so I can do some more stuff with it.

I think his loop is much more cleaner than mine is, but I just wanted to test it out for myself.
Don't judge meeeeee

This script just plots the points out and selects the peaks and valleys and give them green and red respectively. See it as a visual aid. :P

<?php

$plot = array(10,9,8,8,9,7,6,5,4,6,7,8,11,10,12,14,16,20,30,29,28,29,27,25,20,18,15,10,8,5,4,1);

$res = local_extremes($plot);

function local_extremes(array $array){
    $peaks = array();
    $valleys = array();

    $peak_keys = array();
    $valley_keys = array();

    for($i = 0; $i < count($array); $i++){
        $more_than_last = $array[$i] > $array[$i-1];
        $more_than_next = $array[$i] > $array[$i+1];

        $next_is_equal = $array[$i] == $array[$i+1];

        if($next_is_equal) continue;

        if($i == 0){
            if($more_than_next){
                $peaks[] = $array[$i];
                $peak_keys[] = $i;
            }else{
                $valleys[] = $array[$i];
                $valley_keys[] = $i;
            }
        }elseif($i == (count($array)-1)){
            if($more_than_last){
                $peaks[] = $array[$i];
                $peak_keys[] = $i;
            }else{
                $valleys[] = $array[$i];
                $valley_keys[] = $i;
            }
        }else{
            if($more_than_last && $more_than_next){
                $peaks[] = $array[$i];
                $peak_keys[] = $i;
            }elseif(!$more_than_last && !$more_than_next){
                $valleys[] = $array[$i];
                $valley_keys[] = $i;
            }
        }
    }

    return array("peaks" => $peaks, "valleys" => $valleys, "peak_keys" => $peak_keys, "valley_keys" => $valley_keys);
}
?>

<style type="text/css">
    .container{
        position: absolute;
    }

    .point{
        position: absolute;
        width: 4px;
        height: 4px;
        background: black;
    }

    .extreme{
        position: absolute;
        top: 5px;
    }

    .extr_low{
        background: red;
    }

    .extr_high{
        background: green;
    }
</style>

<?php

//Plot
echo "<div class='container'>";
foreach($plot as $key => $point){
    $left = ($key*10);
    $top = 400 - ($point*10);

    if(in_array($key, $res['peak_keys']) || in_array($key, $res['valley_keys'])){
        $extreme = "<div class='extreme'>$point</div>";
    }else{
        $extreme = "";
    }

    if(in_array($key, $res['peak_keys'])){
        $xc = "extr_high";
    }elseif(in_array($key, $res['valley_keys'])){
        $xc = "extr_low";
    }else{
        $xc = "";
    }

    echo "<div class='point $xc' style='left: ".$left."px; top: ".$top."px;'>$extreme</div>";
}
echo "</div>";

?>

<table>
    <tr>
        <th>&nbsp;</th>
        <th>Valley</th>
        <th>Peak</th>
    </tr>
    <tr>
        <th>Lowest</th>

        <td><?php echo min($res['valleys']); ?></td>
        <td><?php echo min($res['peaks']); ?></td>
    </tr>
    <tr>
        <th>Highest</th>
        <td><?php echo max($res['valleys']); ?></td>
        <td><?php echo max($res['peaks']); ?></td>
    </tr>
</table>

I didn't test this much, and it won't really work with anything with fewer than 3 point, but this should give you a good starting point.

<?php

$array = array(10,9,8,8,9,7,6,5,4,6,7,8,11,10,12,14,16,20,30,29,28,29,27,25,20,18,15,10,8,5,4,1);

$extremes = array();
$last = null;
$num = count($array);
for($i=0;$i<$num - 1;$i++) {
    $curr = $array[$i];
    if($last === null) {
        $extremes[] = $curr;
        $last = $curr;
        continue;
    }

    //min
    if($last > $curr && $curr < $array[$i + 1]) {
        $extremes[] = $curr;
    }
    //maxes
    else if ($last < $curr && $curr > $array[$i + 1]) {
        $extremes[] = $curr;
    }
    if($last != $curr && $curr != $array[$i + 1]) {
        $last = $curr;
    }
}
//add last point
$extremes[] = $array[$num - 1];

print_r($extremes);

Gives you the results (you missed a couple in your list):

Array
(
    [0] => 10
    [1] => 8
    [2] => 9
    [3] => 4
    [4] => 11
    [5] => 10
    [6] => 30
    [7] => 28
    [8] => 29
    [9] => 1
)

If you want to make it be exactly like you're list, you'll have to apply some smoothing to the data, or some tolerances to the detection.

A variation on the first derivative test for identifying local extrema, identify points where the delta alternates sign from one interval to the next. These points will be maxima if the delta goes from positive to negative and minima if the delta goes from negative to positive, but for your use that doesn't seem to matter. Also, throw in the end points because the interval is considered open for this test and you seem to want them included.

<?php
define('MSB_MASK', (int)(PHP_INT_MAX + 1));
$points = array(0, 0, 1, 0, -1, -2, -2, -3, -4, -3, 0, 1);
$extrema = getExtrema($points);

function getExtrema($points) {
    $extrema = array($points[0]);
    $limit= count($points)-2;
    for ($i = 0; $i < $limit; ++$i) {
        $deltai = $points[$i+1]-$points[$i];
        $deltaiplus1 = $points[$i+2]-$points[$i+1];
        if (($deltai ^ $deltaiplus1) & MSB_MASK)
            $extrema[] = $points[$i+1];
    }
    $extrema[] = $points[$limit+1];
    return $extrema;
}
?>

Note: I tested a little on ideone.com, it works, but it may have undetected issues. This should work for floats just as well.

Credit: This is the first derivative test from every Calculus I textbook, adapted only slightly for discrete maths. We are treating every point as a critical point because we don't know the function for the graph.

Edit: After looking at a plot of the data on wolframalpha I am thinking that maybe you are just looking for the global max and min on the closed interval, plus the end points? If so, just use something simple, like max($points) and min($points).

Edit: I have never had a good opportunity to use xor before!

yes, for each number in row you compare it with side numbers and you got it ( you need that number smaller then number before and after). Then add numbers first and last number and it is whole.

I expect it is some sorted array.

Here's the pseudo-code for doing this

input: listOfNumbers

//Handle exceptional cases
if listOfNumbers.length == 0
  return []
if listOfNumbers.length == 1
  return [listOfNumbers[0]]

//Pre-condition listOfNumbers.length > 1
extremes = emptyList
lastNumber = listOfNumbers[0]
isIncreasing = listOfNumbers[0] < listOfNumbers[1]
extremes.push(listOfNumbers[0])
foreach number in listOfNumbers[1...listOfNumbers.length]
  if(isIncreasing AND lastNumber > number)
    extremes.push(lastNumber)
    isIncreasing = false
  if(NOT isIncreasing AND lastNumber < number)
    extremes.push(lastNumber)
    isIncreasing = true

extremes.push(listOfNumbers.length-1) 
return extremes

I think this would do it, although I haven't tested this.

Get the first and last number from your numbers array, then sort the array and grab the first and last that differ your your last result. And you will have a result like your example.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!