使用PHP获取没有数组循环的最近值

时间:2014-03-30 21:30:37

标签: php arrays sorting random numbers

给定一个数组:

   $foo = Array(
         99=>'Lowest Numbers',
        123=>'Low Numbers',
        456=>'High Numbers',
        777=>'Highest Numbers',
   );

...和值' 144',我想返回最接近的低值,并且最接近的高值,而不必循环遍历数组中的每个元素,因为实际的数组非常大。

' 144'的预期结果将是123 =>'低数字'

我目前有以下代码:

    function name($color,$fuzzy=false) {
        global $resene;
        $long = 0;
        if(is_array($color)) {
            $long = Color::hex2lng(Color::rgb2hex($color));
        } else {
            $long = Color::hex2lng($color);
        }

        if(isset($resene[$long])) {
            echo $resene[$long];
        } else {
            if($fuzzy) {
                $resene[$long] = '';
                ksort($resene);

                // This is where I am having problems
                                    // The array is sorted, so it should be a simple
                                    // matter of getting the previous and next value
                                    // somehow since we know the position we want to
                                    // start at exists because it has been added.

                echo 'No color name found';
            }
        }
    }

基本上,这个概念非常简单,因为我们正在注入未在数组中找到的值,我们知道它存在。对键进行排序,现在可确保两个最接近的键与搜索的数字最匹配。

上述功能实际上是基于Hex或RGB颜色的搜索,转换为基数10(长值)。

数组中的键是非增量的,这意味着它不是0,1,2,3,即使我已经翻转了'数组,然后键将成为字符串,再次,没有任何增量来获得最接近的。

我正在考虑拆分或其他什么,但是这似乎根据有多少元素拆分数组 - 而不是基于密钥。

这实际上是完成这项工作的最后一步 - 无需遍历每个元素。

非常感谢任何帮助。

这是我写的静态函数的Pastbin,它使用颜色的Long值作为Key返回Colors的一个数组(),Value是颜色的String Name。

Color Index Array

2 个答案:

答案 0 :(得分:4)

正如thelolcat指出的那样,你可能不需要担心这里的性能,但你可以试试二进制搜索的变种。这里没有办法跳过搜索,因为你无法切入ksort()。这里只是我想出的快速草稿:

//$resene is your input ksort()-ed array, $long is the key which position and neighbours you're trying to find
$keys = array_keys($resene);
$min = reset($keys);
$s = key($keys);   // = 0
$max = end($keys);
$e = key($keys);   // = count($resene)
do {
    $guess = $s + (int)(($long - $min)/($max - $min)*($e - $s));
    if ($keys[$guess] > $long) {
        $e = $guess - 1;
        $max = $keys[$e];
        $min = $keys[++$s];
    } elseif ($keys[$guess] < $long) {
        $s = $guess + 1;
        $min = $keys[$s];
        $max = $keys[--$e];
    }
} while ($keys[$guess] != $long && $e != $s);
echo 'Me = '.$keys[$guess].'; prev = '.$keys[$guess - 1].'; next = '.$keys[$guess + 1];

我运行了一些测试,在一个包含0到5,000,000的20,000个随机数的数组上,随机目标值为该数组,我在3-4个循环中获得了一个命中。当然不要忘记检查上一个/下一个是否存在。

如果你可以使用普通的索引数组并在其上使用普通的sort()来避免使用array_keys()重复数组,它会更好。我猜你试图在这里使用键只是为了获得一些速度而你在数组值中没有任何有用的东西?如果是这样,您应该切换到索引数组。

如果你不使用k / sort()并使用类似的东西来找到首先插入新值的位置,你可以让它工作得更好。然后你可以使用array_splice()来插入它,你就已经知道了它的位置,因此,prev / next。

<强>更新

在您的示例中查看方法2后,您尝试做的事情变得更加清晰。我很好奇我在PHP中可以提出多少索引,所以这里有一个函数,可以得到与你相同的结果:

function fast_nearest($array, $value, $exact=false) {
    if (isset($array[$value])) {
        // If exact match found, and searching for exact (not nearest), return result.
        return array($value => $array[$value], 'exact' => true);
    } elseif ($exact || empty($array)) {
        return false;
    }
    // else
    $keys = array_keys($array);
    $min = $keys[0];
    $s = 0;
    $max = end($keys);
    $e = key($keys);
    if ($s == $e) {
        // only one element, it's closest
        return array_merge($array, array('exact' => false));
    } elseif ($value < $min) {
        return array($min => $array[$min], 'exact' => false);
    } elseif ($value > $max) {
        return array($max => $array[$max], 'exact' => false);
    }
    $result = false;
    do {
        $guess = $s + (int)(($value - $min) / ($max - $min) * ($e - $s));
        if ($guess < $s) {
            // oops, off the scale; we found it
            $result = $keys[$s];
        } elseif ($guess > $e) {
            $result = $keys[$e];
        } elseif ($keys[$guess] > $value && $keys[$guess - 1] < $value) {
            // found range
            $result = (($value - $keys[$guess - 1]) < ($keys[$guess] - $value)
                ? $keys[$guess - 1]
                : $keys[$guess]);
        } elseif ($keys[$guess] < $value && $keys[$guess + 1] > $value) {
            $result = (($value - $keys[$guess]) < ($keys[$guess + 1] - $value)
                ? $keys[$guess]
                : $keys[$guess + 1]);
        } elseif ($keys[$guess] > $value) {
            // narrowing search area
            $e = $guess - 1;
            $max = $keys[$e];
        } elseif ($keys[$guess] < $value) {
            $s = $guess + 1;
            $min = $keys[$s];
        }
    } while ($e != $s && $result === false);
    if ($result === false) {
        throw new Exception("Math laws don't work in this universe.");
    }
    return array($result => $array[$result], 'exact' => false);
}

我在顶部的函数中编译了散布的大部分退出场景,并且我还删除了将一个项插入到数组中,因为它不会在函数外部持续存在。您可以轻松地在找到的位置添加array_splice()

我对两个函数(你和我的)进行了速度测试,以便在1到1,000,000,000之间的随机数组上进行比较(是的,两个函数都输入相同的输入):

  • 20,000件物品:
    • fast_nearest() - 7.3 ms 1,000次运行的平均值
    • nearest() - 207 ms 相同1,000次运行的平均值
  • 200,000件物品:
    • fast_nearest() - 70 ms 10次运行的平均值(对不起,1,000人太长了等待这个尺寸)
    • nearest() - 2,798 ms 平均10次
  • 2,000,000件物品:
    • fast_nearest() - 937 ms 平均2次
    • nearest() - 22,156 ms 平均2次

显然,两者都不能在大型阵列上运行良好,所以如果你必须操纵那么多数据 - 我建议使用像数据库服务器这样的东西和正确的索引,PHP不是适合它的工具。

答案 1 :(得分:1)

下面:

$input = 142;
$offset = 0;

while(true){
  if(isset($foo[$input - $offset])){
    $found = array($input - $offset => $foo[$input - $offset]);
    break;
  }  

  if(isset($foo[$input + $offset])){
    $found = array($input - $offset => $foo[$input + $offset]);
    break;
  }      

  $offset++;
}

它应该比标准循环便宜一点

那个阵列到底有多大?为什么速度很重要?

修改:

NVM。你的问题是错的。我刚刚在ksort()上对100K元素的关联数组进行了时序测试。这需要0.07秒。虽然一个完整的foreach循环,需要0.01秒!