【问题标题】:How can I make this PHP function more efficient at scale?如何使这个 PHP 函数在规模上更高效?
【发布时间】:2019-10-26 05:29:08
【问题描述】:

我不知道如何提高效率,并且正在应对编码挑战。有什么提示吗?

目标是返回数组中的唯一值。

Test Conditions Which Code Fails

function solution($A) {

    foreach ($A as $key => $value) {

        $searchResults = array_keys($A, $value);

        //print "Total number of $value found in Array is: " . count($searchResults) . "\n";

        $checkNumber = count($searchResults);

        if ($checkNumber == 1) {

            //print "Unique value is: $value\n";
            return $value;

        }

        //print "\n";

    }

}

【问题讨论】:

  • 这是一个有趣的问题。提示可能是at scale,因为 for 循环是线性的。那么如何通过在规模上也不太昂贵的方式预处理数据来减少迭代。
  • 当你的函数发现第一次出现的值在整个数组中不重复时,它应该返回$value
  • $value 的类型是什么?
  • @jibsteroos 他的功能是通过最有效的方式找到不重复的值。它不一定必须在第一次出现时返回,因为这实际上是他的代码现在所做的。
  • 或nvm。我想我已经知道如何优化而且我不需要知道类型。但是让我去 PC...

标签: php performance


【解决方案1】:

我能想到的最简单的方法是首先使用array_count_values() 来计算每个值的出现次数,然后沿着结果循环并返回出现 1 次的第一个项目。如果没有找到,这也会返回 false...

function solution($a) {
    $counts = array_count_values($a);
    foreach ( $counts as $value => $count ) {
        if ( $count == 1 )  {
            return $value;
        }
    }
    return false;
}

array_count_values() 将遍历整个数组一次(在所有情况下都必须执行 AFAIK),foreach 循环将遍历结果直到找到一个项目。

编辑: 如果您打算使用对象作为数据,您可以通过序列化数据然后按照与上述相同的过程轻松解决此问题。使用unserialize()返回数据...

function solution($a) {
    $ser = array_map("serialize", $a);
    $counts = array_count_values($ser);
    foreach ( $counts as $value => $count ) {
        if ( $count == 1 )  {
            return unserialize($value);
        }
    }
    return false;
}

【讨论】:

  • 我相信这使用了哈希方法。在发布之前,我查看了 array_count_values() 的 C 代码。最多有2个循环。第二个循环(你的 foreach)一旦遇到就会停止。绝对是一个进步。
  • 我不知道 array_count_values() 但现在知道这样的函数存在是件好事。为什么要重新创建轮子。
  • @GetSet,PHP 有很多功能很难全部记住。一段时间后,尽管知道某些东西很可能存在是一个很好的起点,然后开始搜索手册。
【解决方案2】:

好的,所以我有一个不同的解决方案(一个不断破坏输入数组的解决方案),似乎比 OP 快 30%。但后来我把 Nigel Ren 的解决方案放在了替补席上,这个解决方案统治了所有这些。

编辑:除了任的解决方案有一个警告。它不适用于非标量,因为它将值用作数组键。虽然 OP 和我的解决方案确实如此(如果我们使用 strict=true 调整 array_keys 调用,令我惊讶的是,使用 strict=true 它会变得有点慢???)。

<?php

function solution(array $A) {

    foreach ($A as $key => $value) {

        $searchResults = array_keys($A, $value, true);

        //print "Total number of $value found in Array is: " . count($searchResults) . "\n";

        $checkNumber = count($searchResults);

        if ($checkNumber == 1) {

            //print "Unique value is: $value\n";
            return $value;

        }

        //print "\n";

    }
    return null;
}

function solutionRen($a) {
    $counts = @array_count_values($a); //disable warning when running over nonscalars
    foreach ( $counts as $value => $count ) {
        if ( $count == 1 )  {
            return $value;
        }
    }
    return false;
}

function solutionSlepic(array $A)
{
    while (!empty($A)) {
        $value = \array_shift($A);

        $keys = \array_keys($A, $value, true);

        if (empty($keys)) {
            return $value;
        }

        foreach ($keys as $key) {
            unset($A[$key]);
        }
    }
    return null;
}

$range = \range(1,20000);
$input = \array_merge($range, $range, [1000001]);
$input = \array_merge([1000001], $range, $range);
$input = \array_merge($range, $range);
$input = \array_merge(\array_fill(0, 20000, 1), \array_fill(0, 20000, 2));

$input = [];
for($i=0; $i<20000; ++$i) {
    $input[$i] = $input[20000 + $i] = new \stdClass();
}
$input[] = (object) ['unique' => true];

$start = \microtime(true);
$solutionOP = solution($input);
$timeOP = \microtime(true) - $start;
echo "OP: $solutionOP ({$timeOP})\n";

$start = \microtime(true);
$solutionRen = solutionRen($input);
$timeRen = \microtime(true) - $start;
echo "Ren: $solutionRen ({$timeRen})\n";

$start = \microtime(true);
$solutionSlepic = solutionSlepic($input);
$timeSlepic = \microtime(true) - $start;
echo "slepic: $solutionSlepic ({$timeSlepic})\n";

各种输入的输出:

// unnique valus is on end
OP: 1000001 (1.7094209194183)
Ren: 1000001 (0.00097393989562988)
slepic: 1000001 (1.1519079208374)

// unique value is the first
OP: 1000001 (0.00011515617370605)
Ren: 1000001 (0.0009620189666748)
slepic: 1000001 (0.00069785118103027)

// unique value not found among 20k distinct values, each twice in the set
OP:  (1.728000164032)
Ren:  (0.00064802169799805)
slepic:  (1.18425989151)

// unque value not found among 2 distinct values, each 20k times in the set
OP:  (6.4909980297089)
Ren:  (0.00011396408081055)
slepic:  (0.0016219615936279)

// 20000 distinct objects, each twice in the array and one unique on end
OP: (4.8111519813538)
stdClass Object
(
    [unique] => 1
)
// Ren's solution is not capable of working with types other then those that can be keys of array, and so it didnt find the solution and instead yells 40k php warning which i have muted.
Ren: (0.013867139816284)
slepic: (2.5294151306152)
stdClass Object
(
    [unique] => 1
)

【讨论】:

  • 不错的基准测试。因此可以得出结论,array_count_values() 已经具有内置于第一个所需循环的效率。第二个必要的循环只是线性搜索。如果唯一值在数组的末尾,则 OP 的解决方案存在循环 N * N 的缺陷。
  • @GetSet 是的,差不多。顺便说一句,我又添加了一个输入集,其中一个不存在唯一值,但该集仅包含 2 个不同的值,每个重复 20000 次。无论如何,虽然我没有检查过,但我怀疑我的解决方案是最不详尽的内存,如果它值得的话......
  • 我个人认为可以推断内存使用在解决方案需求中不是问题。效率可以是很多事情。然而,考虑到 OP 的解决方案是 N * N,可以推断出速度。我认为内存不相关。正如您的基准所显示的那样,最快的解决方案是相关的。
  • 我的赞成票不是因为你发现我的答案最快,而是你花时间展示了不同方法的相对表现:)
  • @slepic 一旦我们收到 OP 的回复,我们肯定会知道。很高兴您没有(并且请不要)完全丢弃它,因为我个人仍在研究您的代码,并且对于我或其他人来说,它可能会被分叉成一个完全不同(即使相关)的解决方案。从技术上讲,您的解决方案是(以某种方式)链接列表,这对于其他不太明显的事情很有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-12
  • 1970-01-01
  • 2018-02-18
  • 2022-01-24
  • 2018-05-19
  • 1970-01-01
相关资源
最近更新 更多