为什么使用&&比if ... fi快75倍,以及如何使代码更清晰


38

我有以下工作代码:

largest_prime=1
for number_under_test in {1..100}
do
  is_prime=true
  factors=''
  for ((divider = 2; divider < number_under_test-1; divider++));
  do
    remainder=$(($number_under_test % $divider))
    [ $remainder == 0 ] && [ is_prime ] && is_prime=false && factors+=$divider' '
  done
  [ $is_prime == true ] && echo "${number_under_test} is prime!" || echo "${number_under_test} is NOT prime (factors= $factors)"  [ $is_prime == true ] && largest_prime=$number_under_test
done
printf "\nLargest Prime= $largest_prime\n"

这段代码迅速运行了0.194秒。但是,我发现它&& is_prime= false有点难以阅读,并且(未经训练的人)看起来好像是在测试它,而不是在设置它。所以我试图改变&&if...then在14.48秒但慢75倍-而这个作品。在较高的数字上最明显。

largest_prime=1
for number_under_test in {1..100}
do
  is_prime=true
  factors=''
  for ((divider = 2; divider < number_under_test-1; divider++));
  do  
    remainder=$(($number_under_test % $divider))
    if ([ $remainder == 0 ] && [ $is_prime == true ]); then
      is_prime=false
      factors+=$divider' '
    fi  
  done
  [ $is_prime == true ] && echo "${number_under_test} is prime!" || echo "${number_under_test} is NOT prime (factors= $factors)"  [ $is_prime == true ] && largest_prime=$number_under_test
done  
printf "\nLargest Prime= $largest_prime\n"

有没有让块变得清晰而又没有缓慢的东西?

更新(2015年1月4日美国东部时间上午10:40)

很好的反馈!我现在正在使用以下内容。还有其他反馈意见吗 ?

largest_prime=1
separator=' '
for number_under_test in {1..100}; {
  is_prime=true
  factors=''
  for ((divider = 2; divider < (number_under_test/2)+1; divider++)) {
    remainder=$(($number_under_test % $divider))
    if [ $remainder == 0 ]; then
      is_prime=false
      factors+=$divider' '
    fi
  } 
  if $is_prime; then
    printf "\n${number_under_test} IS prime\n\n"
    largest_prime=$number_under_test
  else
    printf "${number_under_test} is NOT prime, factors are: "
    printf "$factors\n"
  fi
}
printf "\nLargest Prime= $largest_prime\n"

1
在旁注中,运行脚本会将其打印Largest Prime= 100在我的计算机上。
朱利奥·穆斯卡洛洛

3
另外,在旁注中,如果您对效率感兴趣,一种提高效率的简单方法是仅迭代到,number_under_test/2而不是最多number_under_test-1:没有任何因数n的因数大于n / 2,因此您仍然会找到所有这样做可以计算非素数。(另外,如果您只想测试素数,则迭代到sqrt(n)就足够了,但是Bash
始终

亚光,好点(+1)。唯一的变化是数字4无效,所以我不得不考虑到这(number_under_test/2)+1一点
Michael Durrant 2015年

1
在你的更新版本中,支架{}是不是真的需要后then,因为该条款then已作为分组操作(沿elifelsefi)。实际上,在某些shell中,您可以编写例如for i in 1 2 3; { echo $i; }no dodone
乔纳森·莱夫勒

1
+1乔纳森(Jonathan),我进行了更改并更新了更新
Michael Durrant

Answers:


66

这是因为您每次都会生成一个子外壳:

if ([ $remainder == 0 ] && [ $is_prime == true ]); then

只需删除括号

if [ $remainder == 0 ] && [ $is_prime == true ]; then

如果要对命令进行分组,请在当前的 shell中使用以下语法:

if { [ $remainder == 0 ] && [ $is_prime == true ]; }; then

(需要结尾的分号,请参见手册

请注意,[ is_prime ]这与[ $is_prime == true ]:可以一样简单地编写$is_prime(不带括号),这将调用bash内置truefalse命令。
[ is_prime ]是一个带有一个参数(字符串“ is_prime”)的测试-当[给定单个参数时,如果参数为非空且该文字字符串始终为非空,则结果为成功,因此始终为“ true”。

为了提高可读性,我将更改很长的一行

[ $is_prime == true ] && echo "${number_under_test} is prime!" || echo "${number_under_test} is NOT prime (factors= $factors)"  [ $is_prime == true ] && largest_prime=$number_under_test

if [ $is_prime == true ]; then
  echo "${number_under_test} is prime!"
else 
  echo "${number_under_test} is NOT prime (factors= $factors)"
  # removed extraneous [ $is_prime == true ] test that you probably
  # didn't notice off the edge of the screen
  largest_prime=$number_under_test
fi

不要低估空格以提高清晰度。


1
有一个错字- largest_prime=$number_under_test应该在then分支中(原来的错误是相同的)
JJoao 2015年

1
还值得注意的是,在bash中,zsh等人[正在调用一个名为的程序[,而该程序[[是在shell中实现的-因此它将更快。尝试time for ((i = 0; $i < 1000; i++)); do [ 1 ]; done与进行比较[[。有关更多信息,请参见此SO问题
kirb

2
bash工具[,它是内置的。在shell提示下,键入type -a [help [
glenn jackman

@glennjackman哇;没有意识到这一点。我认为仍然是这样,因为which [仍然返回/usr/bin/[。我也刚刚意识到我暗示zsh是相同的。对我而言,确实告诉我这是内置的。但是然后...为什么[[更快?
kirb 2015年

2
@glennjackman command -v是另一个不错的which选择。也可以在这里看到。
Abbafei 2015年

9

我认为您正在为自己的功能而努力。考虑:

unset num div lprime; set -- "$((lprime=(num=(div=1))))"
while [     "$((     num += ! ( div *= ( div <= num   ) ) ))" -eq \
            "$((     num *=   ( div += 1 )   <= 101   ))" ]    && {
      set   "$(( ! ( num %      div )         * div   ))"     "$@"
      shift "$(( !    $1 +    ( $1 ==  1 )    *  $#   ))"
}; do [ "$div" -gt "$num" ] && echo "$*"      
done

Shell算法非常有能力自行评估整数条件。它很少需要太多的测试和/或外部分配。这个while循环非常好地复制了嵌套循环:

当然,它的打印量不是很多,但是,例如,将上限设置为16,而不是上面写的101,那么...

2
3
4 2
5
6 3 2
7
8 4 2
9 3
10 5 2
11
12 6 4 3 2
13
14 7 2
15 5 3

肯定是在做工作。它几乎不需要其他任何东西就能近似您的输出:

...
do [ "$div" -eq "$num" ] && shift &&
   printf "$num ${1+!}= prime.${1+\t%s\t%s}\n" \
          "factors= $*"                        \
          "lprime=$(( lprime = $# ? lprime : num ))"
done

只是这样做而不是echo和...

1 = prime.
2 = prime.
3 = prime.
4 != prime.     factors= 2      lprime=3
5 = prime.
6 != prime.     factors= 3 2    lprime=5
7 = prime.
8 != prime.     factors= 4 2    lprime=7
9 != prime.     factors= 3      lprime=7
10 != prime.    factors= 5 2    lprime=7
11 = prime.
12 != prime.    factors= 6 4 3 2        lprime=11
13 = prime.
14 != prime.    factors= 7 2    lprime=13
15 != prime.    factors= 5 3    lprime=13

这适用于busybox。它非常便携,快速且易于使用。

您的subshel​​l问题将在大多数shell中发生,但到目前为止,在bashshell中最为严重。我交替做

( [ "$div" -gt "$num" ] ) && ...

...以及我在上面几个外壳中将其编写为101的上限的方式,dash并在.017秒内完成了不带子壳的工作,而在1.8秒内完成了不带子壳的工作。busybox.149和2,zsh .149和4,bash.35和6,以及ksh93.149和.160。ksh93不会像其他外壳程序那样为子外壳程序分叉。因此,也许问题不仅仅在于子外壳,还在于它是外壳


[ "$((...))" -eq "$((...))" ]over 的优点是(( (...) == (...) ))什么?后者难携带吗?
ruakh 2015年

@ruakh-便携性,速度,可靠性。[ "$((...))" -eq "$((...)) ]可以在不需要 15秒即可运行该程序的shell中运行,而另一个则不需要。如果一个人与另一个人之间的优势完全值得怀疑,那么那只能给前者带来优势,这意味着永远没有充分的理由使用它(( (...) == (...) ))
mikeserv

抱歉,您的回复似乎假设我已经对的shell支持有了详细的了解(( ... ))。我受宠若惊,但我没有这方面的详细知识。(请记住,我是只问是否(( ... ))便携性较弱的人。)因此,我真的无法理解您的答复。:-/您能说清楚点吗?
ruakh 2015年

@ruakh-对不起...我没有看到您在问它是否更具便携性,以及它的优势-这就是为什么我回答了可移植性。无论如何,"$((...))"POSIX指定的,另一个是Shell扩展。POSIX shell非常强大。甚至dashposh都会正确处理分支测试,例如"$((if_true ? (var=10) : (var=5) ))"始终$var正确分配。busybox在此处中断-无论其$if_true值如何,它总是会评估双方。
mikeserv

@ruakh-天哪 我今天一定要休息一下...它说就在那里... 后者的便携性较弱吗?我猜以前没看过...?
mikeserv
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.