写像 in シェル


## 写像とは

データを変換する演算と, 「データ列」を与えると, データ列の全ての要素に変換を施したデータ列を返すような演算は, 多くのプログラミング言語において, 組み込みで, または, 基本ライブラリで提供される大変汎用的な演算です.

言語によって `map`, `collect` などの名前で提供されています. 本記事では, 便宜的に「写像」と呼びます.
「写像」という呼称は, 数学や計算機科学では(さらに汎用的な意味で)一般的ですが, プログラミングでは, 自身のよく使う言語における関数/メソッドの名前で呼ぶ方が多いと思います.

多くの繰り返し処理は写像に抽象化されます.

```
a = <新しい空のデータ列>;
for (i = 0; i < <データ列>.長さ; i++) {
  a.append(<変換>(<データ列>[i]));
}
return a;
```

のような形の演算は

```
<写像> <変換> <データ列>
```

のように抽象化できます.

また, 写像として抽象化すると, データ列全体を読み込んで処理する必要がなく, 非常に大きなデータ列に対してシーケンシャルに処理が可能であるという利点もあります. (実装によりますが, あくまでこの抽象化において原理的には.)

以下

* Ruby の `collect`
* JavaScript の `map`
* Clojure の `map`

で例示します.

以下の環境で試験しました.

```
% irb --version
irb 0.9.6(09/06/30)

% node --version
v0.10.26

% java -jar clojure-1.7.0.jar
Clojure 1.7.0
user=>
```

### 数値の写像

数値列中の数値を全て自乗します.

#### Ruby

```irb:
irb(main):001:0> [3, 1, 4, 1, 5, 9, 2, 6].collect{|i| i * i}
=> [9, 1, 16, 1, 25, 81, 4, 36]
```

#### JavaScript

```js:
> [3, 1, 4, 1, 5, 9, 2, 6].map(function(x){return x*x})
[ 9,
  1,
  16,
  1,
  25,
  81,
  4,
  36 ]
```

#### Clojure

```clj:
user=> (map #(* % %) [3 1 4 1 5 9 2 6])
(9 1 16 1 25 81 4 36)
```

### 文字列->数値の写像

文字列の列に含まれる全ての文字列をその文字列の長さに変換します.

#### Ruby

```irb:
irb(main):002:0> ["May", "I", "have", "a", "large", "container", "of", "coffee"].collect{|s| s.length}
=> [3, 1, 4, 1, 5, 9, 2, 6]
```

#### JavaScript

```js:
> ["May", "I", "have", "a", "large", "container", "of", "coffee"].map(function(s){return s.length})
[ 3,
  1,
  4,
  1,
  5,
  9,
  2,
  6 ]
```

#### Clojure

```clj:
user=> (map count ["May" "I" "have" "a" "large" "container" "of" "coffee"])
(3 1 4 1 5 9 2 6)
```

### 文字列->文字列の写像

文字列の列に含まれる全ての文字列を, 元の文字列の後ろに元の文字列を反転した文字列を連結したものに変換します.

#### Ruby

```irb:
irb(main):003:0> ["May", "I", "have", "a", "large", "container", "of", "coffee"].collect{|s| s+s.reverse()}
=> ["MayyaM", "II", "haveevah", "aa", "largeegral", "containerreniatnoc", "offo", "coffeeeeffoc"]
```

#### JavaScript

```js:
> ["May", "I", "have", "a", "large", "container", "of", "coffee"].map(function(s){return s+s.split("").reverse().join("")})
[ 'MayyaM',
  'II',
  'haveevah',
  'aa',
  'largeegral',
  'containerreniatnoc',
  'offo',
  'coffeeeeffoc' ]
```

#### Clojure

```clj:
user=> (map #(apply str (concat % (reverse %))) ["May" "I" "have" "a" "large" "container" "of" "coffee"])
("MayyaM" "II" "haveevah" "aa" "largeegral" "containerreniatnoc" "offo" "coffeeeeffoc")
```

## シェルで

前置きが長くなりました.

写像を実装するスクリプトを用意し, 実行例を示したいと思いますが, 実はスクリプトを別途用意しなくても, `xargs -n 1 sh -c ''` などでも写像は実現できます.

`xargs` を使う場合,

* 元のデータ列を区切り文字でいくつずつ区切るか固定になってしまう
* 最初の引数が `$1` でなく `$0` になってしまう
* 少々長くなる

など, 小さなデメリットはいくつかありますが, 別スクリプトに依存しないという大きなメリットがありますので, 場合によりますが, どちらかというと `xargs` の方が可搬性が高いと思います.

以下では, 写像を実装するスクリプト `map` による方法と `xargs` による方法の両方で例示します.

写像を実装する Bourne Shell スクリプトは以下のようになります.

```sh:map
#!/bin/sh

f=$1
while read line
do
  set "$line"
  echo `eval $f`
done
```

* 変換は, 第一引数に「引数を一つとり変換した値を返すプログラム」を与えます.
* データ列は, 改行区切りで標準入力から与えられるものとします.

上記を `map` として PATH 環境変数に含まれるディレクトリに保存 (または保存したディレクトリを PATH に追加) し, 実行パーミッションを与えてあるものとして, 以下使い方を例示します.

```sh:
$/bin/sh --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin15)
Copyright (C) 2007 Free Software Foundation, Inc.
```

で試験しました.

### 数値の写像

数値列中の数値を全て自乗します.

`map` スクリプトを用いて

```sh:
$ echo "3 1 4 1 5 9 2 6" | tr ' ' '\n' | map 'expr $1 \* $1'
9
1
16
1
25
81
4
36
```

または `xargs` を用いて

```sh:
$ echo "3 1 4 1 5 9 2 6" | tr ' ' '\n' | xargs -n 1 sh -c 'expr $0 \* $0'
9
1
16
1
25
81
4
36
```

のようにします. 後者は引数が `$0` から始まるので注意です.

(上記は

```
$ echo "3 1 4 1 5 9 2 6" | xargs -n 1 sh -c 'expr $0 \* $0'
```

と同等ですが, `echo ... | tr ' ' '\n'` 部分は, 改行区切りデータを試験用に生成する部分で, 実際には改行区切りの入力があった場合の残りの処理を示すため, 上記のように記載しました.)

### 文字列->数値の写像

文字列の列に含まれる全ての文字列をその文字列の長さに変換します.

```sh:
$ echo "May I have a large container of coffee" | tr ' ' '\n' | map 'echo ${#1}'
3
1
4
1
5
9
2
6
```

または

```sh:
$ echo "May I have a large container of coffee" | tr ' ' '\n' | xargs -n 1 sh -c 'echo ${#0}'
3
1
4
1
5
9
2
6
```

のようにします.

### 文字列->文字列の写像

文字列の列に含まれる全ての文字列を, 元の文字列の後ろに元の文字列を反転した文字列を連結したものに変換します.

```sh:
$ echo "May I have a large container of coffee" | tr ' ' '\n' | map 'echo $1`echo $1 | rev`'
MayyaM
II
haveevah
aa
largeegral
containerreniatnoc
offo
coffeeeeffoc
```

または

```sh:
$ echo "May I have a large container of coffee" | tr ' ' '\n' | xargs -n 1 sh -c 'echo $0`echo $0 | rev`'
MayyaM
II
haveevah
aa
largeegral
containerreniatnoc
offo
coffeeeeffoc
```

のようにします.

ちなみに　`rev` は文字列を反転するコマンドではなく, (改行区切りで) 文字列の列に含まれる全ての文字列を反転するコマンドであり, `map` の機能を内包しています. 従って反転だけであれば

```sh:
$ echo "May I have a large container of coffee" | tr ' ' '\n' | rev
yaM
I
evah
a
egral
reniatnoc
fo
eeffoc
```

で十分です.

というより, UNIX/シェル文化ではむしろ, このような写像機能を内包したプログラムの方が一般的かもしれません.

お読みいただきありがとうございました.