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分EST)

素晴らしいフィードバック!現在、次のものを使用しています。別のフィードバックはありませんか ?

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
また、副次的に、効率に興味がある場合、これを改善する簡単な方法の1つnumber_under_test/2は、最大number_under_test-1値ではなく最大値まで反復することです。nの係数はn / 2より大きくないので、すべてを見つけることができます。これを行うことにより、非素数の要因。(また、素数のテストのみに関心がある場合は、sqrt(n)まで反復するだけで十分ですが、Bashには平方根を計算する組み込み関数がありません。)
Malte Skoruppa

マット、良い点(+1)。唯一の変更は、4番では機能しなかっ(number_under_test/2)+1たことです。そのため、それを考慮に入れなければなりませんでした
Michael Durrant

1
あなたの更新されたバージョンでは、中括弧は{}本当に後に必要とされていないthenので、句then、すでに(と一緒にグループ化演算子として機能しelifelseまたはfi)。実際、一部のシェルでは、たとえばfor i in 1 2 3; { echo $i; }no doまたはで書くことができますdone
ジョナサンレフラー

1
ジョナサン+1、これらの変更を加えて更新を更新しました
マイケルデュラント

回答:


66

それは、サブシェルを毎回生成しているためです。

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

かっこを削除するだけ

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

コマンドをグループ化する場合、現在のシェルでそれを行う構文があります。

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

(末尾のセミコロンが必要です。マニュアルを参照してください)

以下[ is_prime ]と同じではないことに注意してください[ $is_prime == true ]$is_primebashビルトインtrueまたはfalseコマンドを起動する(括弧なしで)単純に書くことができます。
[ is_prime ]文字列「is_prime」の引数が1つのテストです。[単一の引数を指定すると、引数が空でない場合に結果が成功し、そのリテラル文字列は常に空ではないため、常に「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
タイプ

1
また、bashの、zshの、らに、ということは注目に値します[文字通りと呼ばれるプログラムを起動している[のに対し、[[シェルで実装されている-ので、それはより速くなります。time for ((i = 0; $i < 1000; i++)); do [ 1 ]; doneと比較してみてください[[。詳細については、このSOの質問を参照しください。
kirb

2
bash implements [、それは組み込みです。シェルプロンプトからtype -a [help [
グレン・ジャックマン

@glennjackmanうわー; それを知らなかった。which [まだ戻ってくるのでまだそうだと思いました/usr/bin/[。また、zshが同じであることを暗示していることにも気付きました。私にとってそれは組み込みだと教えてくれます。しかし、...なぜ[[は速いですか?
kirb

2
@glennjackmanはもうcommand -v1つの優れたwhich代替手段です。こちらもご覧ください
アッバフェイ

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

シェル演算は、整数条件を単独で評価できます。あまりにも多くのテストや外部割り当てが必要になることはめったにありません。この1つのwhileループは、ネストされたループをかなりうまく複製します。

もちろんそれほど多くは印刷されませんが、それほど多くは書きませんでしたが、たとえば、上記のように天井を101ではなく16に設定すると...

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ます。非常にポータブルで、高速で、使いやすいです。

サブシェルの問題はほとんどのシェルで発生しますが、それは断然bashシェルで最も深刻です。交互に行う

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

...そして、上で101の天井のためにいくつかのシェルでそれを書いた方法でdash、サブシェルなしで.017秒で、サブシェルで1.8秒でした。busybox.149と2、zsh .149とbash4、0.35と6、およびksh93.149と.160。ksh93他のシェルのようにサブシェルをフォークしません。したがって、問題はサブシェルではなく、シェルである可能性があります


[ "$((...))" -eq "$((...))" ]over の利点は何(( (...) == (...) ))ですか?後者は移植性が低いですか?
ルアフ

@ruakh-移植性、速度、信頼性。プログラムを実行するのに15秒はかからない[ "$((...))" -eq "$((...)) ]シェルで動作し、もう一方は動作しません。あるものが他のものよりも有利であることがまったく疑わしい場合、それは前者に優位性を与えることができるだけです。つまり、を使用する正当な理由はありません(( (...) == (...) ))
mikeserv

申し訳ありませんが、あなたの返事は、私がすでにシェルのサポートに関する詳細な知識を持っていることを前提としているようです(( ... ))。私はお世辞になりましたが、その詳細な知識はありません。(覚えておいて、私はちょうど(( ... ))携帯性が低いかどうかを尋ねた人です。)だから私は本当にあなたの返事を理解することはできません。:-/もう少しはっきりさせていただけますか?
ルアック

@ruakh-申し訳ありませんが、あなたはそれがよりポータブルであるかどうかを尋ねているのを見ませんでした。とにかく、"$((...))"POSIXで指定され、もう1つはシェル拡張です。POSIXシェルは非常に優れています。でdashあり、poshブランチテストを正しく処理し"$((if_true ? (var=10) : (var=5) ))"、常に$var正しく割り当てます。busyboxそこで中断します- $if_trueの値に関係なく、常に両側を評価します。
mikeserv

@ruakh-ああ男。今日は少しオフにする必要があり...それは...右が言う後者の少ないポータブルですか?私は前にそれを見なかった、私は推測する...?
mikeserv
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.