更多罗马数字和Bash
在罗马时:完成罗马数字转换器脚本。
在我的上一篇文章中,我开始深入研究一个经典的计算机科学难题:将罗马数字转换为阿拉伯数字。首先,更准确的说法应该是印度-阿拉伯数字,值得一提的是,它被认为是在公元一世纪到四世纪之间发明的——一种基于 0..9 值的计数系统。
上次我完成的脚本提供了分析指定罗马数字的基础知识,并通过这个简单的函数将每个值转换为其十进制等价值
mapit() {
case $1 in
I|i) value=1 ;;
V|v) value=5 ;;
X|x) value=10 ;;
L|l) value=50 ;;
C|c) value=100 ;;
D|d) value=500 ;;
M|m) value=1000 ;;
* ) echo "Error: Value $1 unknown" >&2 ; exit 2 ;;
esac
}
然后我演示了一种巧妙的方法,使用未被充分利用的 seq
命令逐字符解析字符串,但遗憾的是,您将无法将其用于最终的罗马数字到阿拉伯数字转换器。为什么?因为根据情况,脚本有时需要向前跳两个位置,而不仅仅是线性地从左到右,一次一个字符。
相反,您可以将主循环构建为 while 循环
while [ $index -lt $length ] ; do
our code
index=$(( $index + 1 ))
done
在解决这个算法难题时,需要考虑两种基本情况:后续值大于当前值,或者不是——例如,IX 与 II。第一个是 9(字面意思是 10 减去 1),第二个是 2。这并不奇怪;您需要知道脚本中的当前值和下一个值。
敏锐的读者已经意识到序列中的最后一个字符是一个特殊情况,因为不会有下一个值可用。我将首先忽略特殊情况,并在稍后的代码开发中解决它。敬请关注,敏锐的朋友们!
由于 Bash shell 脚本没有优雅的内联函数,因此获取当前值和下一个值的代码不会是 value=mapit(romanchar)
,但它会有点笨拙,因为它使用了全局变量 value
mapit ${romanvalue:index-1:1}
currentval=$value
mapit ${romanvalue:index:1}
nextval=$value
关键是要意识到,在下一个值不大于当前值的情况下(例如,MC),您不能自动断定下一个值不会是复杂的双值序列的一部分。像这样:MCM。您不能只说 M=1000 和 C=500,所以我们将其转换为 1500,并在稍后处理第二个 M。MCM=1900,而不是 2500!
基本逻辑结果非常简单明了
if [ $nextval -gt $currentval ] ; then
sum=$(( $sum + $nextval - $currentval ))
else
sum=$(( $sum + currentval ))
fi
完成!
或者,嗯,不是。上面条件代码的问题在于,在您引用了当前值和下一个值的情况下,您需要确保在下次循环中不再处理下一个值。
换句话说,当序列“CM”被转换时,M 不应在第二次循环中再次被转换。
这正是我放弃 for 循环的原因,这样您就可以使某些循环迭代为 +1 迭代,而其他循环迭代为 +2 迭代(视情况而定)。
考虑到这一点,让我们在条件语句中添加必要的行
if [ $nextval -gt $currentval ] ; then
sum=$(( $sum + $nextval - $currentval ))
index=$(( $index + 1 ))
else
sum=$(( $sum + currentval ))
fi
请记住,while 循环的最底部仍然有索引值增量 +1。上面添加到条件语句中的内容基本上是,当遇到下一个值 > 当前值的情况时,脚本将处理这两个值并向前跳过一个额外的字符。
这意味着对于任何给定的罗马数字,循环的次数将等于或小于序列中字符的总数。
这意味着现在问题已经解决,除了序列中的最后一个值。如果它不是下一个-当前对的一部分会发生什么?最简单的情况,您如何解析“X”?
这需要大量的代码来绕过从字符串转换 nextval
(这将失败,因为它超出了字符串的长度)以及对 nextval
的任何测试引用。
这暗示了一个简单的解决方案:将整个 if-then-else 代码块包装在一个测试最后一个字符的条件中
if [ $index -lt $length ] ; then
if-then-else code block
else
sum=$(( $sum + $currentval ))
fi
就是这样。天哪,我想你明白了!这是完整的 while 语句,以便您了解这如何融入整体程序逻辑
while [ $index -le $length ] ; do
mapit ${romanvalue:index-1:1}
currentval=$value
if [ $index -lt $length ] ; then
mapit ${romanvalue:index:1}
nextval=$value
if [ $nextval -gt $currentval ] ; then
sum=$(( $sum + $nextval - $currentval ))
index=$(( $index + 1 ))
else
sum=$(( $sum + $currentval ))
fi
else
sum=$(( $sum + $currentval ))
fi
index=$(( $index + 1 ))
done
结果证明它根本不是特别复杂。关键是要认识到您需要以相当集中的方式解析罗马数字,而不是逐个字母解析。
让我们快速测试一下这个脚本
$ sh roman.sh DXXV
Roman number DXXV converts to Arabic value 525
$ sh roman.sh CMXCIX
Roman number CMXCIX converts to Arabic value 999
$ sh roman.sh MCMLXXII
Roman number MCMLXXII converts to Arabic value 1972
任务完成。
在我的下一篇文章中,我计划研究这个编码挑战的反面,将阿拉伯数字转换为罗马数字序列。换句话说,您输入 99,它返回 XCIX。为什么不在等待的时候自己尝试编写代码呢?