【问题标题】:bash script to find total number of permuations用于查找排列总数的 bash 脚本
【发布时间】:2017-01-02 17:06:32
【问题描述】:

我正在编写一个bash 脚本来查找总数。排列的(用于数学) 其公式是 n!/(n-r)!,但我使用的脚本给出的输出值与预期的不同。有人能找出我的错误吗?我是bash 脚本的新手。

echo "Enter no. to find factorial"
read num 
fact=1
while [ $num -gt 0 ]
do
fact=`expr $num \* $fact`
num=`expr $num - 1`
done

echo "Enter value for r"
read num1
num2=$((num-num1))

fact1=1
while [ $num2 -gt 0 ]
 do
fact1=`expr $num2 \* $fact1`
num2=`expr $num2 - 1`
done

echo "Total no. of permutations $((fact/fact1)) "

【问题讨论】:

  • 欢迎来到 *。你的问题对我来说听起来像是一个家庭作业问题。如果是这样,你就会通过寻求解决方案来剥夺学习的机会。在您的情况下,您破坏了学习如何调试问题的机会。看看meta.stackexchange.com/questions/10811/… 也可能会有所帮助。
  • 当问题陈述很简单时,很难提供解决方案,“它不起作用”。请edit您的问题更完整地描述您预期会发生什么以及这与实际结果有何不同。请参阅 How to Ask 以获取有关什么是好的解释的提示。

标签: linux bash shell permutation


【解决方案1】:

您的主要问题是您在第一个阶乘循环中破坏了$num 的值,但是您尝试在循环后再次使用它。 $num 的值在所述循环之后总是以零结束,因此 $((num-num1)) 最终将是负数,而条件 $num2 -gt 0 在第一次评估时将始终为 false。您可以通过复制变量并销毁副本来解决此问题。

其他建议:

  • 当您不需要插值时,使用单引号字符串而不是双引号,因为单引号字符串完全不允许插值。这样比较安全。人们总是因为意外的双引号插值而被烧毁。这是一个很好的例子:Assignment issue with Rscript -e calls
  • 最好使用$[...] 而不是$((...)) 进行算术计算,因为这样可以节省两个字符。
  • 切勿使用`expr ...` 进行算术评估,因为这需要分叉并且使解析复杂化(例如,您必须使用反斜杠转义星号,这是不可取的)。 (而且我不知道为什么你有时会选择那个习语,因为你显然知道算术表达式的 $((...)) 构造。)我建议你始终使用 $[...] 进行所有算术评估。
  • 条件句最好使用[[ ... ]],因为它比[ ... ] 更强大,并且由于是shell 关键字而具有更简洁的语法,而单括号命令只是一个内置命令。 (如果您不相信我,请尝试运行type [ [[。)即使您不需要双括号命令的更高级功能,您仍然应该使用它,以保持所有代码的一致性,并且再次,为了更简洁的语法。
  • 由于乘以 1 无效,您可以将 -gt 0 测试更改为 -gt 1

#!/bin/bash

## get n
echo 'Enter no. to find factorial';
read n;

## compute n!
nFact=1;
temp1=$n;
while [[ $temp1 -gt 1 ]]; do
    nFact=$[temp1*nFact];
    temp1=$[temp1-1];
done;

## get r
echo 'Enter value for r';
read r;

## compute (n-r)!
nrFact=1;
temp1=$[n-r];
while [[ $temp1 -gt 1 ]]; do
    nrFact=$[temp1*nrFact];
    temp1=$[temp1-1];
done;

echo "Total no. of permutations $[nFact/nrFact]";

【讨论】:

  • "$[...] 是不应再使用的旧语法。" 根据 Bash 的手册和 *.com/a/2415777/1671066。我建议您编辑您的答案并重写相关部分。
  • @bgoldst 不,我没有对你投反对票,我为什么要投反对票,你的回答似乎对我有帮助。 (但你问得很好,看看它是多么容易假设。)然而,我不同意你的观点和推理。 (希望你同意,你的愤怒被误导了。)
【解决方案2】:

无需调用另一个程序 (expr),因为 Bash 完全能够计算整数算术(参见 man bash 中的 ARITHMETIC EVALUATION)。以下 sn-p 可能会对您有所帮助:

fact=1     
for ((i=4; i>1; i--)) do
  ((fact*=i))
done
echo $fact

#echo $((4+4))  # Arithmetic Expansion might also be useful

输出

24

【讨论】:

    【解决方案3】:

    我认为OP的代码和@bgoldst解决方案可以改进,因为当nr很大时两者都会失败,因为算法的关键是使用阶乘,正如您应该知道的,这会产生bash 无法处理的巨大整数。 bash 算术仅限于 32 位有符号整数。

    例如,假设 n 是 49,r 是 2,@bgoldst 的脚本显示 -6 -- 正确的值是 2352 (49 *48)。当 n 为 32 且 r 为 2 时,输出为 0 -- 正确的值为 992 (32*31)。

    @bgoldst 改进了 OP 的代码,但他没有为算法增加智能——你忘了我们是程序员吗?

    数学背景

    表达式n!/(n-r)!可以这样重写

      n!      n·(n-1)·(n-2)···(n-r+1) · (n-r)!
    ------ = --------------------------------- = n·(n-1)·(n-2)···(n-r+1)
    (n-r)!              (n-r)!
    

    注意r的值其实是最终相乘的因子个数。

    例如,当 n=49r=2

       49!       49!      49·48·47!
    --------- = ----- = ------------- = 49·48 = 2352
     (49-2)!     47!         47!
    

    我的解决方案

    我的解决方案使用 bc 工具来绕过 32 位定点算术的约束,并使用 seq 工具来移除任何 shell 循环。

    假设 nr 值是格式良好的正整数:

    # Check if n>=r
    if   [ "$( bc <<<$n'>='$r 2>/dev/null)" != 1 ]
    then
         echo "r cannot be bigger than n"
         exit 1
    fi
    
    PERMUTATIONS=$( seq -s '*' $((n-r+1)) "$n" | bc )
    
    echo "Total no. of permutations for n=$n and r=$r is ${PERMUTATIONS:-1}"
    

    注释:

    • bc &lt;&lt;&lt;$n'&gt;='$r 2&gt;/dev/null 打印 0 或 1,无论 nr 有多大。如果 nr 不是格式正确的整数值,bc 命令将声明,报告错误消息并且不打印任何内容(NULL 字符串) .请注意,如果我的代码必须允许bash 无法正确处理的大整数,那么它不能包含bash 常用的算术比较,例如[ "$n" -ge "$r" ][[ "$n" &gt;= "$r" ]](( n &gt;= r ))。这就是为什么这里也使用bc

    • seq -s '*' 会产生,例如,4*5*6*7,它通过管道传送到bc 命令。

    • r 为零时,无论 n 是多少,seq 命令行都不会产生任何内容(空)。因此,PERMUTATIONS="${PERMUTATIONS:-1}" 需要提供默认值 (1)。

    • bc 命令(以及dc)在几行中打印大量数字(请阅读手册页);因此,我的代码将按原样打印

    【讨论】: