シェル芸 Project Euler - Problem 17

## はじめに

@gin_135 さんの「[Project Eulerをシェル芸で解いてみる](http://qiita.com/gin_135/items/c0fde5686f33d947aa6c)」シリーズが大変面白いです.
大抵は @gin_135 さんの回答, @eban さんの別解を楽しく読ませていただき, 読んだだけで分からないものは, 手元で再現などして楽しんでいますが, 方針として大きく異なる別解を思いついたものについて, コメント欄に長文も失礼ですので, 自分のエントリを立てようと思います.

「Project Euler」の解説, 問題文の引用などは省略します.

本エントリは Problem 17 ([問題原文](https://projecteuler.net/problem=17), [問題文日本語訳](http://odz.sakura.ne.jp/projecteuler/index.php?cmd=read&page=Problem%2017)) の解答です.

## 部分問題「英語化」の解答

数値を英語に, -例えば 342 を "three hundred and forty-two" に- 変換するシェルスクリプト (1 以上 1000 以下の自然数のみ対応) を独立して用意し, 全体解答はこれを用いて解きます.

```sh:number.sh
#!/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 では使えません.

```shell-session
$ /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
```

## 全体問題の解答

```shell-session
$ 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)](http://qiita.com/gin_135/items/85f8b026b4261a4ed361)」
@eban さんの解答「[Project Euler Problem 17 #シェル芸 - jarp](http://jarp.does.notwork.org/diary/201601c.html#201601213)」

----

## 配列使わない版

追記 2016/01/26

`number.sh` の bash の配列を使わない版も書いてみました.

### numberb.sh

```sh: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
```

### 実行例

```shell-session
$ ./numberb.sh 342
three hundred and forty-two
$ ./numberb.sh 115
one hundred and fifteen
```

### 原理

`set` を使い, 文字列を空白で区切って, `$1`, `$2`, ... に代入し, 必要回数 `shift` して, `$1` を参照することで, 擬似的に配列を実現します.

```shell-session
$ set a b c d e f
    # $1=a, $2=b, ... となっている
$ shift 3
    # 3回シフト, $1=d, $2=e, ... となっている.
$ echo $1
d
```