bash中的递归函数

时间:2012-03-13 10:45:52

标签: bash function recursion

我想做一个能在bash中返回数字的阶乘的函数

这是当前不起作用的代码,任何人都可以告诉我什么是错的以及如何纠正它?我刚开始学习bash,我不知道那么多。

#!/bash/bin
factorial()
{
  let n=$1
  if (( "$n" <= "1" ))
  then return 1
  else
  factorial n-1
  return $n*$?
  fi
  return 0
}
factorial 5
echo "factorial 5 = $?"

8 个答案:

答案 0 :(得分:16)

#!/bin/bash

function factorial() 
{ 
   if (( $1 < 2 ))
   then
     echo 1
   else
     echo $(( $1 * $(factorial $(( $1 - 1 ))) ))
   fi
}

这会更好。

(无论如何,它最多可以达到25,这应该足以证明关于递归的观点。)

对于更高的数字,bc将是使用的工具,使第九行成为上述:

echo "$1 * $(factorial $(( $1 - 1 )))" | bc

但你必须要小心bc -

$ factorial 260
38301958608361692351174979856044918752795567523090969601913008174806\
51475135399533485285838275429773913773383359294010103333339344249624\
06009974551133984962615380298039823284896547262282019684886083204957\
95233137023276627601257325925519566220247124751398891221069403193240\
41688318583612166708334763727216738353107304842707002261430265483385\
20637683911007815690066342722080690052836580858013635214371395680329\
58941156051513954932674117091883540235576934400000000000000000000000\
00000000000000000000000000000000000000000

对我糟糕的系统造成了很大压力!

答案 1 :(得分:3)

echo - 结果可能是获得n&gt;结果的唯一方法5,但捕获echo的结果需要一个子shell,这意味着递归会很快变得昂贵。更便宜的解决方案是使用变量:

factorial() {
    local -i val=${val:-($1)}
    if (( $1 <= 1 )); then
        echo $val
        return
    fi
    (( val *= $1 - 1 ))
    factorial $(( $1 - 1 ))
}

如果您想要确保在启动时未设置val,请使用包装功能:

factorial() {
    local -i val=$1
    _fact() {
        if (( $1 <= 1 )); then
            echo $val
            return
        fi
        (( val *= $1 - 1 ))
        _fact $(( $1 - 1 ))
    }
    _fact $1
}

进行比较:

# My Method
$ time for i in {0..100}; do factorial $(( RANDOM % 21 )); done > /dev/null 

real    0m0.028s
user    0m0.026s
sys     0m0.001s

# A capturing-expression solution
$ time for i in {0..100}; do factorial $(( RANDOM % 21 )); done > /dev/null 

real    0m0.652s
user    0m0.221s
sys     0m0.400s

答案 2 :(得分:1)

使用echo代替return

的另一个实现
#!/bin/bash

factorial()
{
        if [ $1 -le 1 ]
        then
                echo 1
        else
                echo $[ $1 * `factorial $[$1-1]` ]
        fi
}
echo "factorial $1 = " `factorial $1`

答案 3 :(得分:1)

clear cat

fact()

{

        i=$1
        if [ $i -eq 0 -o $i -eq 1 ]
        then
                echo 1
        else
                f=`expr $i \- 1`
                f=$(fact $f)
                f=`expr $i \* $f`
                echo $f
        fi
}

read -p "Enter the number : " n

if [ $n -lt 0 ]

then

        echo "ERROR"

else

        echo "THE FACTORIAL OF $n : $(fact $n) "
fi

答案 4 :(得分:1)

递归地计算因子

除了所有答案,我还想建议:

存储计算的阶乘以避免重新计算

如果你打算多次运行这个函数,使用数组来存储计算的阶乘可以大大改进你的函数!!

为此,我们需要反向递归的方式,以便存储每个计算的阶乘:

declare -ia _factorials=(1 1)
factorial() {
    local -i _req=$1 _crt=${2:-${#_factorials[@]}} _next=_crt+1 \
        _factor=${3:-${_factorials[@]: -1}}
    if [ "${_factorials[_req]}" ]; then
        printf %u\\n ${_factorials[_req]}
    else
        printf -v _factorials[_crt] %u $((_factor*_crt))
        factorial $1 $_next ${_factorials[_next]}
    fi
}

然后

factorial 5
120

好的,并且:

declare -p _factorials
declare -ai _factorials=([0]="1" [1]="1" [2]="2" [3]="6" [4]="24" [5]="120")

然后,如果我执行 set -x 来跟踪操作并要求更高的值:

set -x
factorial 10
+ factorial 10
+ local -i _req=10 _crt=6 _next=_crt+1 _factor=120
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 720
+ factorial 10 7
+ local -i _req=10 _crt=7 _next=_crt+1 _factor=720
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 5040
+ factorial 10 8
+ local -i _req=10 _crt=8 _next=_crt+1 _factor=5040
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 40320
+ factorial 10 9
+ local -i _req=10 _crt=9 _next=_crt+1 _factor=40320
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 362880
+ factorial 10 10
+ local -i _req=10 _crt=10 _next=_crt+1 _factor=362880
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 3628800
+ factorial 10 11
+ local -i _req=10 _crt=11 _next=_crt+1 _factor=3628800
+ '[' 3628800 ']'
+ printf '%u\n' 3628800
3628800

510 进行递归,直接使用阶乘 5。

declare -p _factorials
declare -ai _factorials=([0]="1" [1]="1" [2]="2" [3]="6" [4]="24" [5]="120"
                  [6]="720" [7]="5040" [8]="40320" [9]="362880" [10]="3628800")

避免分叉:通过设置变量替换echo

declare -ia _factorials=(1 1)
factorialtovar() {
    local -i _req=$2 _crt=${3:-${#_factorials[@]}} _next=_crt+1 \
        _factor=${4:-${_factorials[@]: -1}}
    if [ "${_factorials[_req]}" ]; then
        printf -v $1 %u ${_factorials[_req]}
    else
        printf -v _factorials[_crt] %u $((_factor*_crt))
        factorialtovar $1 $2 $_next ${_factorials[_next]}
    fi
}

然后

factorialtovar myvar 7 ; echo $myvar
5040

declare -p myvar _factorials 
declare -- myvar="5040"
declare -ai _factorials=([0]="1" [1]="1" [2]="2" [3]="6" [4]="24" [5]="120" [6]="720" [7]="5040")

set -x
factorialtovar anothervar 10
+ factorialtovar anothervar 10
+ local -i _req=10 _crt=8 _next=_crt+1 _factor=5040
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 40320
+ factorialtovar anothervar 10 9
+ local -i _req=10 _crt=9 _next=_crt+1 _factor=40320
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 362880
+ factorialtovar anothervar 10 10
+ local -i _req=10 _crt=10 _next=_crt+1 _factor=362880
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 3628800
+ factorialtovar anothervar 10 11
+ local -i _req=10 _crt=11 _next=_crt+1 _factor=3628800
+ '[' 3628800 ']'
+ printf -v anothervar %u 3628800
set +x
+ set +x

echo $anothervar
3628800

答案 5 :(得分:1)

避免分叉!

此处正确处理进程线程的唯一答案是 kojiro's answer,但我不喜欢为此使用全局变量。

为此,我们可以使用参数来传输步骤或中间结果:

factorial() { 
        if [ $1 -gt 1 ]; then
            factorial $(( $1 - 1 ))   $(( ${2:-1} * $1 ))
        else
            echo $2
        fi
}

没有forks,结果作为第二个参数传递。

避免分叉 (bis)

然后为了避免分叉,我更喜欢将结果存储到变量而不是使用echo(或其他printf):< /p>

setvarfactorial() { 
    if [ $2 -gt 1 ]; then
        setvarfactorial $1 $(( $2 - 1 )) $(( ${3:-1} * $2 ))
    else
        printf -v "$1" %u $3
    fi
}

用法:

factorial 5
120

setvarfactorial result 5
echo $result
120

当目标是将结果存储到变量中时,第二种方法比使用系统友好更快

result=$(factorial 5)
# or same
result=`factorial 5`

分叉的比较。

没有变量集,只是运行10x factorial 20

在我的旧树莓派上测试:

time for i in {1..10};do factorial 20;done|uniq -c
     10 2432902008176640000
real    0m0.376s
user    0m0.159s
sys     0m0.035s


time for i in {1..10};do kojirofactorial 20;done|uniq -c
     10 2432902008176640000
real    0m0.348s
user    0m0.150s
sys     0m0.023s


time for i in {1..10};do danielfactorial 20;done|uniq -c
     10 2432902008176640000
real    0m5.859s
user    0m1.015s
sys     0m1.732s

如果 kojiro 的版本看起来比我的快一点,差异并不重要,但其他答案将花费 10 倍以上的时间!!

查看分叉完成:

set -x
danielfactorial 5
+ danielfactorial 5
+ ((  5 <= 1  ))
++ factorial 4
++ ((  4 <= 1  ))
+++ factorial 3
+++ ((  3 <= 1  ))
++++ factorial 2
++++ ((  2 <= 1  ))
+++++ factorial 1
+++++ ((  1 <= 1  ))
+++++ echo 1
++++ last=1
++++ echo 2
+++ last=2
+++ echo 6
++ last=6
++ echo 24
+ last=24
+ echo 120
120

对比:

factorial 5
+ factorial 5
+ '[' 5 -gt 1 ']'
+ factorial 4 5
+ '[' 4 -gt 1 ']'
+ factorial 3 20
+ '[' 3 -gt 1 ']'
+ factorial 2 60
+ '[' 2 -gt 1 ']'
+ factorial 1 120
+ '[' 1 -gt 1 ']'
+ echo 120
120

答案 6 :(得分:0)

#-----------------factorial ------------------------
# using eval to avoid process creation
fac=25
factorial()
{
    if [[ $1 -le 1 ]]
    then
        eval $2=1
    else

        factorial $[$1-1] $2
        typeset f2
        eval f2=\$$2
        ((f2=f2*$1))
        eval $2=$f2

    fi
}
time factorial $fac res 
echo "factorial =$res"

答案 7 :(得分:0)

为了完成 @Marc Dechico 解决方案,而不是 eval,现在最好传递引用(bash >= 4.3):

factorial_bruno() {
    local -i val="$1"
    local -n var="$2"                             # $2 reference
    _fact() {
        if (( $1 <= 1 )); then
            var="$val"
            return
        fi
        ((val*=$1-1))
        _fact $(($1-1))
    }
    _fact "$1"
}
declare -i res
factorial_bruno 20 res
printf "res=%d\n" "$res"

如果我们比较 @kojiro@techno 的 1,000 次运行的时间(为他们捕获结果,毕竟我们想要结果), @Marc Dechici 和我的解决方案,我们得到:

declare -i res
TIMEFORMAT=$'\t%R elapsed, %U user, %S sys'

echo "Kojiro (not catching result) :"
time for i in {1..1000}; do factorial_kojiro $((i%21)); done >/dev/null
echo "Kojiro (catching result) :"
time for i in {1..1000}; do res=$(factorial_kojiro $((i%21))); done
echo "Techno (not catching result) :"
time for i in {1..1000}; do factorial_techno $((i%21)); done >/dev/null
echo "Techno (catching result, 100% data already cached) :"
time for i in {1..1000}; do res=$(factorial_techno $((i%21))); done
_factorials=(1 1)
echo "Techno (catching result, after cache reset) :"
time for i in {1..1000}; do res=$(factorial_techno $((i%21))); done
echo "Marc Dechico :"
time for i in {1..1000}; do factorial_marc $((i%21)) res; done
echo "This solution :"
time for i in {1..1000}; do factorial_bruno $((i%21)) res; done

Kojiro (not catching result) :
    0.182 elapsed, 0.182 user, 0.000 sys
Kojiro (catching result) :
    1.510 elapsed, 0.973 user, 0.635 sys
Techno (not catching result) :
    0.054 elapsed, 0.049 user, 0.004 sys
Techno (catching result, 100% data already cached) :
    0.838 elapsed, 0.573 user, 0.330 sys
Techno (catching result, after cache reset) :
    2.421 elapsed, 1.658 user, 0.870 sys
Marc Dechico :
    0.349 elapsed, 0.348 user, 0.000 sys

This solution :
    0.161 elapsed, 0.161 user, 0.000 sys

有趣的是,在函数中执行输出 (echo/printf) 并使用 res=$(func...) (subshel​​l) 捕获结果总是非常昂贵,对于 Kojiro 的解决方案,80% 的时间,> 95% 为 Techno one...

编辑:通过添加具有类似解决方案的缓存(@techno 新解决方案 - 不同之处在于使用 printf -v 而不是使用变量引用),如果我们需要多次计算,我们可以进一步提高响应时间阶乘。在 bash 中使用整数限制,这意味着我们需要多次计算相同的阶乘(可能对这样的基准测试有用:-)