Чому використання && в 75 разів швидше, ніж якщо… fi та як зробити код яснішим


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і це працює - але це 75 разів повільніше за 14,48 секунди. Це найбільш помітно на більших числах.

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"

Чи має бути якась чіткість блоку без повільності?

Оновлення (01.04.2015 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
Також на сторонній стороні, якщо ви зацікавлені в ефективності, одним тривіальним способом покращити це було б лише повторення до, number_under_test/2а не до number_under_test-1: Жоден коефіцієнт числа n не перевищує n / 2, тому ви все одно знайдете все чинники для непростих чисел, виконуючи це. (Крім того, якщо вас зацікавило лише тестування на превантність, досить було б ітерації до sqrt (n), але Bash так чи інакше не має вбудованої функції для обчислення квадратних коренів.)
Malte Skoruppa

Матовий, хороший бал (+1). Єдина зміна полягала в тому, що це не працювало на номер 4, тому я повинен був зробити це, (number_under_test/2)+1щоб дозволити це
Майкл Дюрант

1
У вашій оновленої версії, брекети {}не надто потрібні після thenпункту , тому що thenвже служить в якості угруповання оператора (поряд з elif, elseабо fi). Насправді в деяких оболонках ви могли писати, наприклад, for i in 1 2 3; { echo $i; }без 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_prime(без дужок), що викликало б вбудований trueабо falseкоманду bash .
[ is_prime ]- це тест з одним аргументом, рядок "is_prime" - коли [йому надається один аргумент, результат є успішним, якщо аргумент не порожній, а цей буквальний рядок завжди не порожній, отже, завжди "вірно".

Для читабельності я змінив би дуже довгу лінію

[ $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
є Typo - largest_prime=$number_under_testмає бути в тодішній гілці (така ж помилка і в оригіналі)
JJoao

1
Варто також зазначити, що в bash, zsh, та ін, [виклик програми, яка називається буквально [, тоді як [[вона реалізована в оболонці - значить, вона буде швидшою. Спробуйте time for ((i = 0; $i < 1000; i++)); do [ 1 ]; doneі порівняйте [[. Дивіться це питання ТА для отримання додаткової інформації.
kirb

2
Баш інструменти [, це вбудований. Із підказки про оболонку введіть type -a [таhelp [
glenn jackman

@glennjackman Нічого собі; не знав цього. Я припускав, що це все ще так, тому що which [все одно повертається /usr/bin/[. Я також просто зрозумів, що я мав на увазі, що zsh - те саме; для мене це говорить мені, що це вбудований. Але тоді ... чому [[швидше?
kirb

2
@glennjackman command -v- ще одна добра 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

Арифметика оболонки досить здатна самостійно оцінювати цілі умови. Він рідко потребує занадто багато тестів та / або зовнішніх завдань. Цей 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. Це дуже портативний, швидкий та простий у користуванні.

Проблема з вашою нижньою оболонкою відбуватиметься у більшості оболонок, але, безумовно , найгостріша в bashоболонці. Я чергував заняття

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

... і так, як я написав це вище в декількох оболонках для стелі 101 і dashзробив це без нижньої оболонки за 0,117 секунд і з нижньою оболонкою за 1,8 секунди. busybox.149 і 2, zsh .149 і 4, bash.35 і 6, і ksh93в .149 і .160. ksh93не роздрібнюється для передплатників, як повинні бути інші оболонки. Тож може бути проблема не стільки в нижній частині корпусу, скільки в оболонці .


Що перевагу [ "$((...))" -eq "$((...))" ]над (( (...) == (...) ))? Чи останній менш портативний?
ruakh

@ruakh - портативність, швидкість, надійність. [ "$((...))" -eq "$((...)) ]працює в оболонках, яким не потрібно 15 секунд для запуску програми, а інший - ні. Якщо перевага одних над іншими взагалі сумнівна, то це може лише надати перевагу першому, а це означає, що ніколи не буде вагомих причин для використання (( (...) == (...) )).
mikeserv

Вибачте, але, здається, ваша відповідь передбачає, що я вже докладно знаю підтримку оболонки (( ... )). Я задоволений, але я нічого не маю , що детальне знання. (Пам'ятайте, я лише запитав, чи (( ... ))менш портативний.) Тому я не можу зрозуміти вашу відповідь. : - / Чи можете ви бути трохи більш чіткими?
ruakh

@ruakh - Вибачте ... Я не бачив, щоб ви запитували, чи є вона більш портативною, як це було вигідніше - саме тому я відповів про портативність. У будь-якому випадку, "$((...))"вказано POSIX, а інше - розширення оболонки. Оболонки POSIX досить здатні. Навіть dashі poshправильно буде обробляти галузеві тести як "$((if_true ? (var=10) : (var=5) ))"і завжди призначати $varправильно. busyboxперерви там - це завжди зрівняється з обох сторін незалежно від $if_trueцінності.
mikeserv

@ruakh - о людино. Я, мабуть, трохи відключився сьогодні ... там написано прямо ... чи останній менш портативний ? Я цього раніше не бачив, мабуть ...?
mikeserv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.