PHP for循环对于大输入来说太慢了(Digit Sum循环)

时间:2013-11-12 16:13:19

标签: php loops for-loop

亲爱的,我向你征求意见。

我在 PHP

中有以下代码/ for 循环
$number= 123
$counter = 0;
$digitsum = 6 /* This is calculated by another function */

for($i=1;$i<=$number-1;$i++) {
if(array_sum(preg_split('//',$i)) == $digitsum){
  $counter++;
       }    
}

echo $counter; 

基本上,我的循环计算低于给定数字的数字具有相同的数字总和,然后显示有多少数字。即使它与5位数字一起完美地工作并且在1秒内计算出循环次数,我需要它使用18位数字并在1秒内完成计数。

这甚至可以实现吗?或者我应该寻求其他解决方案?

4 个答案:

答案 0 :(得分:2)

这是一个非常快速的解决方案。它仍然不会在1秒内处理18位数字,但它可以在@Edakos(6,12345)的答案中仅用2秒就可以做到。

function count_numbers_with_digit_sum( $sum, $max ) {
    if($sum < 0 || $max <= 0) {
        return 0;
    }
    $count = 0;
    $greatest_piece = (int) str_pad(1, strlen($max.""), "0");
    $first_digit = (int) substr($max, 0, 1);

    if( $max <= 9 && $sum <= 9 ) {
        $count++;
    }
    else {
        $count_to = max(1, ($sum > $first_digit) ? $first_digit : $sum );
        for( $i = 0; $i < $count_to; $i++ ) {
            $count += count_numbers_with_digit_sum( $sum - $i, $greatest_piece - 1 );
        }
        $count += count_numbers_with_digit_sum( $sum - $count_to, $max % $greatest_piece );
    }
    return $count;
}

$start = microtime(true);
echo count_numbers_with_digit_sum( 6, 12345);
echo "<br/>";
echo (int)((microtime(true) - $start)*1000) )." ms";

一些示例运行:

count_numbers_with_digit_sum( 9,  100    ) = 10      0 ms
count_numbers_with_digit_sum( 6,  123    ) = 10      0 ms
count_numbers_with_digit_sum( 6,  12345  ) = 130     2 ms
count_numbers_with_digit_sum( 23, 12345  ) = 530     40 ms
count_numbers_with_digit_sum( 34, 582973 ) = 13923   2287 ms
count_numbers_with_digit_sum( 9,  984930 ) = 2001    39 ms

这是它的工作原理。让我们调用S(x,y)小于或等于y的不同正整数的数量,这些整数的数字总和等于x。例如,S(3,25) = 3(3,12,21)。我们可以做以下逻辑。为简单起见,我们只看x = 6和y = 123,就像你原来的例子一样。 S将是我们不“使用”第一个(数百)数字的整数数,加上我们所做的整数数。此方案中的数百位数的最大值为1,因此......

S(6,123) = S(6,99) + S(5,23)

查看十位数,我们可以使用0到6之间的任何值,所以:

S(6,99) =   S(6,9) // single digits
          + S(5,9) // 10-19
          + S(4,9) // 20-29
          + S(3,9) // 30-39
          + S(2,9) // 40-49
          + S(1,9) // 50-59
          + S(0,9) // 60

这非常适合递归。对于给定的y,我们查看所有最左边的数字为0(即未使用)的场景,如S(x,9),S(x,99),S(x,999),等等,然后我们看看“最大限度”最左边的数字。例如,

S(34, 12345) = S(34, 9999) + S(34, 999) + S(34, 99) + S(34, 9) + S(33, 2345)
             = 10          + 0          + 0         + 0        + S(33, 2345)
             = 10 + S(33, 999) + S(33, 99) + S(33, 9) + S(31, 345)
             = 10 + 0          + 0         + 0        + S(31, 345)
             = 10 + S(31, 99) + S(31, 9) + S(31, 45)
             = 10 + 0         + 0        + 0
             = 10

上面的函数实现了这个递归。您可以轻松地调整此函数以使用大“数字”作为字符串值:

function count_numbers_with_digit_sum_string( $sum, $max ) {
    return count_numbers_with_digit_sum_string_inner( $sum."", $max."" );
}

function count_numbers_with_digit_sum_string_inner( $sum, $max ) {
    if($sum < 0 || $max <= 0) {
        return 0;
    }
    $count = 0;
    $strlenmax = strlen($max);
    $strlensum = strlen($sum);
    $nines_less_than_max = (int) str_pad("", $strlenmax-1, "9");
    $first_digit = (int) substr($max, 0, 1);
    $last_two_digits_sum = (int) substr($sum, -2);

    if( $strlenmax <= 1 && $strlensum <= 1 ) {
        $count++;
    }
    else {
        $count_to = max( 1, ($strlensum > 1 || $sum > $first_digit) ? $first_digit : ( (int) $sum ) );
        for( $i = 0; $i < $count_to; $i++ ) {
            $count += count_numbers_with_digit_sum_string_inner( substr($sum, 0, -2) .($last_two_digits_sum - $i), $nines_less_than_max );
        }
        $count += count_numbers_with_digit_sum_string_inner( substr($sum, 0, -2) .($last_two_digits_sum - $count_to), substr($max, 1));
    }
    return $count;
}
$start = microtime(true);
echo count_numbers_with_digit_sum_string( 34, 12345);
echo "<br>".( (int)((microtime(true) - $start)*1000) )." ms";

这个版本大约是上面纯数字版本的两倍。

您可以通过预先计算和缓存值来极大地提高速度。正如您在上面所看到的,有许多术语,如S(x, 999)S(x,9999)等。只有10个x(0..9)的值“工作”,其中y = 9,19个值(0..18)适用于y = 99,28(0..27)y = 999等。如果你想缓存,比如1,558个值(S的所有组合(x,y)为y in 9,99,999,最高999,999,999,999,999,999),你可以减少大量的递归并节省大量的时间。凭借足够大的缓存,我相信您可以将18位数字的时间缩短到一秒。通过构建它可以很容易地做到(缓存9个值,然后是99,然后是999等)。如果我以后有时间,我会发布概念证明。

希望有所帮助!

(编辑澄清缓存计算,修复$ count_to中的计算错误)

答案 1 :(得分:0)

这应该更快,因为它不使用正则表达式:

<?php

$number = 123;
$counter = 0;
$digitsum = 6; /* This is calculated by another function */

for($i=1;$i<=$number-1;$i++) {
    if(array_sum(str_split($i)) == $digitsum) {
        $counter++; 
    }
}

echo $counter;

答案 2 :(得分:0)

您可以尝试以下选项C:

选项A:(您的尝试)

$number   = 12345;
$counter  = 0;
$digitsum = 6; 

$start = microtime();

for ($i = 1; $i <= $number - 1; $i++) {
    if (array_sum(preg_split('//', $i)) == $digitsum) {
        $counter++; 
    }
}

echo (int)((microtime() - $start)*1000); // ~ 42 miliseconds at writecodeonline.com

选项B:(来自here

$start = microtime();

$i = 0;
while ($i++ < $number) {
    if (array_sum(str_split($i)) == $digitsum) {
        $counter++; 
    }
}

echo (int)((microtime() - $start)*1000); // ~ 19 miliseconds at writecodeonline.com

选项C:(来自here

$start = microtime();

$i = 0;
while ($i++ < $number) {
    $sum = 0;
    $n = $i;
    do {
        $sum += $n % 10;
    } while ($sum <= $digitsum and $n = (int) $n / 10);

    if ($sum == $digitsum) {
        $counter++; 
    }
} 
echo (int)((microtime() - $start)*1000); // ~ 5 miliseconds at writecodeonline.com

答案 3 :(得分:0)

也许这就是你想要的

function foo($digits, $digitsum) {
    $sum = 0;
    if(!empty($digits)) {
        if($digitsum > 1) {
            $current = array_shift($digits);
            if(count($digits) > 0) {
                if($digitsum >= $current) {
                    $sum += foo($digits, $digitsum - $current);
                    for($i = 0; $i < $current; $i++) {
                        $sum += foo(array_fill(0, count($digits), 9), $digitsum - $i);
                    }
                } else {
                    for($i = 0; $i <= $digitsum; $i++) {
                        $sum += foo(array_fill(0, count($digits), 9), $digitsum - $i);
                    }
                }
            } elseif($current >= $digitsum) {
                $sum += 1;
            }
        } elseif($digitsum == 1) {
            $sum += count($digits);
        } else {
            $sum += 1;
        }
    }
    return $sum;
}
$number   = 123;
$digitsum = 6;

$digit = strlen($number);
$digits = str_split($number);
$counter = foo($digits, $digitsum);
var_dump($counter);