はじめに
@gin_135 さんの「Project Eulerをシェル芸で解いてみる」シリーズが大変面白いです.
大抵は @gin_135 さんの回答, @eban さんの別解を楽しく読ませていただき, 読んだだけで分からないものは, 手元で再現などして楽しんでいますが, 方針として大きく異なる別解を思いついたものについて, コメント欄に長文も失礼ですので, 自分のエントリを立てようと思います.
「Project Euler」の解説, 問題文の引用などは省略します.
本エントリは Problem 17 (問題原文, 問題文日本語訳) の解答です.
部分問題「英語化」の解答
数値を英語に, -例えば 342 を "three hundred and forty-two" に- 変換するシェルスクリプト (1 以上 1000 以下の自然数のみ対応) を独立して用意し, 全体解答はこれを用いて解きます.
#!/bin/sh
o=(one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen)
t=(twenty thirty forty fifty sixty seventy eighty ninety)
if [ $1 -lt 20 ]
then
eval echo '${o['`expr $1 - 1`']}'
elif [ $1 -lt 100 ]
then
mod10=`expr $1 % 10`
if [ $mod10 -eq 0 ]
then
eval echo '${t['`expr $1 / 10 - 2`']}'
else
eval echo '${t['`expr $1 / 10 - 2`']}'"-"`eval $0 $mod10`
fi
elif [ $1 -lt 1000 ]
then
mod100=`expr $1 % 100`
if [ $mod100 -eq 0 ]
then
eval echo '${o['`expr $1 / 100 - 1`']} hundred'
else
eval echo '${o['`expr $1 / 100 - 1`']} hundred and '`eval $0 $mod100`
fi
elif [ $1 -eq 1000 ]
then
echo "one thousand"
fi
のようなシェルスクリプトを用意します.
場合分けで
- 20 未満は配列引き
- (20 以上) 100 未満は, 10 で割った商を配列引き, 10 で割った余りを自身の再起呼び出しで処理, 両者を結合
- (100 以上) 1000 未満は, 100 で割った商を配列引き, 100 で割った余りを自身の再帰呼び出しで処理, 両者を結合
- 1000 固定文字列
で解答を出力しています.
変数展開した文字列を含む文字列を eval で再度展開しています.
四則演算は expr にやらせています.
配列を使っているので, オリジナルの Bourne Shell では使えません.
$ /bin/sh --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin15)
Copyright (C) 2007 Free Software Foundation, Inc.
$ ./number.sh 342
three hundred and forty-two
$ ./number.sh 115
one hundred and fifteen
全体問題の解答
$ seq 1 1000 | xargs -n 1 ./number.sh | tr -d '\n -' | wc -m | tr -d ' '
のようにします.
-
seq
で自然数の生成 -
xargs -n 1
で, それぞれの入力行に対して ./number.sh を実行 -
tr
で改行, 空白, ハイフンの除去 -
wc -m
で文字のカウント - 再度
tr
で余分な空白の除去
です.
参考
@gin_135 さんの解答「Project Eulerをシェル芸で解いてみる(Problem 17)」
@eban さんの解答「Project Euler Problem 17 #シェル芸 - jarp」
配列使わない版
追記 2016/01/26
number.sh
の bash の配列を使わない版も書いてみました.
numberb.sh
#!/bin/sh
p=$0
n=$1
o="one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen"
t="twenty thirty forty fifty sixty seventy eighty ninety"
if [ $n -lt 20 ]
then
set $o
shift `expr $n - 1`
echo $1
elif [ $n -lt 100 ]
then
mod10=`expr $n % 10`
set $t
shift `expr $n / 10 - 2`
if [ $mod10 -eq 0 ]; then echo $1; else
eval echo '$1'"-"`eval $p $mod10`
fi
elif [ $n -lt 1000 ]
then
mod100=`expr $n % 100`
set $o
shift `expr $n / 100 - 1`
if [ $mod100 -eq 0 ]; then echo "$1 hundred"; else
eval echo '$1 hundred and '`eval $p $mod100`
fi
elif [ $n -eq 1000 ]
then
echo "one thousand"
fi
実行例
$ ./numberb.sh 342
three hundred and forty-two
$ ./numberb.sh 115
one hundred and fifteen
原理
set
を使い, 文字列を空白で区切って, $1
, $2
, ... に代入し, 必要回数 shift
して, $1
を参照することで, 擬似的に配列を実現します.
$ set a b c d e f
# $1=a, $2=b, ... となっている
$ shift 3
# 3回シフト, $1=d, $2=e, ... となっている.
$ echo $1
d