我想做一个能在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 = $?"
答案 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
从 5
到 10
进行递归,直接使用阶乘 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,结果作为第二个参数传递。
然后为了避免分叉,我更喜欢将结果存储到变量而不是使用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...)
(subshell) 捕获结果总是非常昂贵,对于 Kojiro 的解决方案,80% 的时间,> 95% 为 Techno one...
编辑:通过添加具有类似解决方案的缓存(@techno 新解决方案 - 不同之处在于使用 printf -v
而不是使用变量引用),如果我们需要多次计算,我们可以进一步提高响应时间阶乘。在 bash 中使用整数限制,这意味着我们需要多次计算相同的阶乘(可能对这样的基准测试有用:-)