【问题标题】:Get nearest values without loop in array using PHP使用PHP在数组中获取最接近的值而无需循环
【发布时间】:2014-03-30 21:30:37
【问题描述】:

给定一个数组:

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

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

'144' 的预期结果是 123=>'Low Numbers'

我目前有以下代码:

    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颜色的搜索,转换为base 10(long值)。

数组中的键是非增量的,这意味着它不是 0,1,2,3,即使我“翻转”了数组,键也会是字符串,同样,没有增量可获取最接近的。

我正在考虑拆分或其他什么,但是这似乎是根据元素的数量来拆分数组——而不是基于键。

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

任何帮助将不胜感激。

这是我编写的静态函数的 Pastbin,它使用颜色的 Long 值作为键返回颜色的 Array(),值是颜色的字符串名称。

Color Index Array

【问题讨论】:

  • 如果不使用某种循环,我不确定这是否可行,无论您以何种方式看待它,都需要遍历值
  • 要在没有循环的情况下执行此操作,您必须预先计算所有可能的结果并存储在一个索引中,该索引会在数组更改时更新。如果您已经有一个单独的数组,其中包含从 0 到最大值的所有数字(假设此处为无符号),以及每个数字的最接近答案的数组位置,那么您可以进行查找,它的顺序为 1。否则它的顺序为 n,您需要一个循环。
  • 也许阅读一下en.wikipedia.org/wiki/Divide_and_conquer_algorithm 并考虑使用递归
  • 不管怎样,你对这个东西进行了基准测试吗?这个数组有多大?遍历数组一次通常没什么大不了的,它是线性的。当事情变得 O(n^2) 或更糟时,性能确实往往难以扩展。也正如 Chitowns 所说,您可以将其设为 O(log n) 而不是 O(n)。
  • 如果数组键实际存在,那么“最近”的低/高值是多少(例如,您的示例中的 456)?

标签: php arrays sorting random numbers


【解决方案1】:

正如 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 个循环中得到了成功。当然不要忘记检查 prev/next 是否存在。

此外,如果您可以使用普通的索引数组并在其上使用普通的 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 毫秒 平均 1000 次运行
    • nearest() - 207 毫秒 平均 1000 次运行
  • 200,000 项:
    • fast_nearest() - 70 毫秒平均 10 次运行(抱歉,1000 次等待这个大小的时间太长了)
    • nearest() - 2,798 毫秒平均 10 次运行
  • 2,000,000 项:
    • fast_nearest() - 937 毫秒 平均 2 次运行
    • nearest() - 22,156 毫秒平均 2 次运行

显然,两者在大型数组上都不能很好地工作,因此如果您必须处理这么多数据 - 我建议使用具有适当索引的数据库服务器之类的东西,PHP 不是适合它的工具。

【讨论】:

  • @Dan 好吧,如果您使用 array_search(),您将不需要自己的循环...但这并不意味着循环不存在,它只会由 array_search 在内部执行()。正如每个人已经告诉过你的那样,没有任何循环是无法做到的。
  • @Dan,我已按照您的要求添加了“最接近的匹配”解决方案,从您最初的问题来看,这并不是很清楚。另外,我做你的工作只是为了一个原因——我和一位同事打赌,我​​可以让它在我的本地环境(AMD Athlon 2.71 GHz)上在不到 1 秒的时间内找到 1,000,000 个项目中最接近的匹配项。如果从外部提供有序数字的索引数组,它在没有array_keys() 步骤的情况下工作得非常好。
  • @Dan,只有当您在最初的问题中忘记了ksort() 时,您才会得到错误的结果。我认为这是任意条件。这也会影响速度。无论如何,您在 pastebin 上拥有的绝不是大数组,您应该使用简单的$min=null; $res=null; foreach ($colors as $long =&gt; $name) { if (abs($value-$long) &lt; $min || $min === null) { $min = abs($value-$long); $res = $long; } return //$res... 就可以了,而不会真正浪费时间在 ksort 上。
  • @Dan 我明白了。好吧,那别担心,它会过去的。 :) 现实情况是,有时 PHP 的内部解决方案比您自己编写的解决方案要糟糕得多,即使您的代码无法解释也是如此。此外,您可以查看 KPHP 或 HipHop 等内容。
  • 当然,使用循环会比 ksorting + 搜索数组(顺便说一句也循环)更快。这里的OP是妄想。这个问题很没有效率,有人应该删除它!
【解决方案2】:

这里:

$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。你的问题是错误的。我刚刚在 100K 元素的关联数组上对 ksort() 进行了计时测试。它需要 0.07 秒。而一个完整的 foreach 循环,需要 0.01 秒!

【讨论】:

  • 不,如果您正在执行完整循环,则不需要 ksort,因为您将距离存储在变量中并在它变小时继续更新它
猜你喜欢
  • 2013-03-28
  • 1970-01-01
  • 1970-01-01
  • 2017-08-15
  • 2012-10-30
  • 2012-03-20
  • 1970-01-01
  • 2022-01-25
  • 1970-01-01
相关资源
最近更新 更多