Vimの複数行処理をmap,filterに見立てて理解する


今風？の言語でよくやるリスト処理とVimの複数行処理って似てるじゃん！
って思ってビルド待ち時間とかに書いてみた記事です

Vimの複数行処理（名前は今付けた）って、すっっっごい便利だし大して難しくないのにあまり知られていない気がする...
ので、ちょっと捉え方を変えて説明できるか試してみるよ

ここで言うリスト処理というのは、よくやる
`対象選択(list) -> 条件で絞り込み(filter) -> 変換処理(map)`
のことです

## filterって？
複数の要素の中から`条件`に一致する要素のみを残す処理のこと

### Javaだと
Streamで

```Java
Stream.of("foo", "bar", "baz").filter(s -> s.startswith("b")).collect(Collectors.toList()) // [bar, baz]
```
`s -> s.startswith("b")`の部分が条件

### Pythonだと
内包表記で

```Python
[s for s in ["foo", "bar", "baz"] if s.startswith('b')] # ['bar', 'baz']
```
`s.startswith('b')`の部分が条件

### じゃあVimだと？
Vimの複数の要素と条件とはなんだろうか
要素は行、条件は検索文字列（正規表現含む）と置き換えて考えるとうまくはまる

<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/dc910233-f7fd-f311-385c-864c983e0d26.png" width="400px">
検索クエリの`/^b`が条件
（結果はハイライトされている2行）

## mapって？
複数の要素それぞれに`変換処理`を適用する

### Javaだと
やはりStreamで

```Java
Stream.of("foo", "bar", "baz").map(s -> s + "!").collect(Collectors.toList()) // [foo!, bar!, baz!]
```
`s -> s + "!"`の部分が変換処理

### Pythonだと
こちらも内包表記で

```Python
[s + '!' for s in ["foo", "bar", "baz"]] # ['foo!', 'bar!', 'baz!']
```
`s + '!'`の部分が変換処理

### じゃあVimだと？
Vimで言う変換処理は普段のキーボード操作だ（普段とはnormalモードのこと）
normalモードで`A!`とすると行末に移動して!を挿入するので、`A!`の部分が変換処理

## filterとmapの合わせ技
JavaやPythonだと当然組み合わせて色々出来ちゃうが、Vimでも同じなのだ！

### Javaだと
```Java
Stream.of("foo", "bar", "baz").filter(s -> s.startswith("b")).map(s -> s + "!").collect(Collectors.toList()) // [bar!, baz!]
```

### Pythonだと
```Python
[s + '!' for s in ["foo", "bar", "baz"] if s.startswith('b')] # ['bar!', 'baz!']
```

### で、Vimだと？
```Vim
:%g/^b/normal A!
```
...！
見慣れないかもしれないが、なんのことはない！
`/`で分けてひとつずつ見てみると
#### `%g`
+ 対象選択だ
 + Javaで言う`Stream.of("foo", "bar", "baz")`にあたる
+ `%`は全行を意味するVimの記号だ
+ Visualモードで`:`を押すと`'<,'>`となり、反転している行という意味になる
+ ほかにも`.`や`$`という記号もあるが、`%`と範囲選択だけ覚えていれば問題はない
+ `%v`というのもあり、条件に一致しなかった行を対象とする
 + `grep`と`grep -v`から来ているのかな？
 + 下の方にサンプルがあるよ

#### `^b`
+ 条件での絞り込みだ
 + Javaで言う`.filter(s -> s.startswith("b"))`にあたる

#### `normal A!`
+ 変換処理だ
 + Javaで言う`.map(s -> s + "!")`にあたる
+ normalモードで`A!`をストロークしたことにする、という意味になる

#### だから
つなげて読むと一緒でしょ？
`Stream.of("foo", "bar", "baz").filter(s -> s.startswith("b")).map(s -> s + "!")`
`:%g/^b/normal A!`

これが
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/89a18dec-d436-7a2c-cc9d-b68eed3571f0.png" width="400px">

こうなる
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/94c5e8a0-f9db-1599-d605-8bef6c261101.png" width="400px">

## で、何がうれしいのって？　なんでもできるよ！！！
### 空行を削除したり
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/fd974501-0a96-a1ac-d4b9-435e239cf457.png" width="400px">
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/0edba398-a9a5-7c08-5aad-e05c96327419.png" width="400px">

### 行末の`;`がない行にだけ`;`をつけたり
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/4b595bf8-ce41-336e-b5c9-85921f267614.png" width="400px">
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/17580a23-9168-c0bc-0b73-8d6d176206ee.png" width="400px">

### todo行を下に集めたり
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/72f8a581-7c3e-7519-fcff-6a3c4aacfb36.png" width="400px">
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/7944ec97-c355-edb1-0408-f35151519dea.png" width="400px">

### Logのいらない部分を削ぎ落としたり
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/d5c411d3-a28e-56ba-31b5-200421ad6d7d.png" width="400px">
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/e719483c-9e16-10a9-7c15-335ea1fe5b14.png" width="400px">

### csvのカラムを入れ替えたり
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/62e00c95-1aee-abb2-8257-3f58b5287478.png" width="400px">
<img src="https://qiita-image-store.s3.amazonaws.com/0/113398/b6dee319-b9f4-6441-881b-523ee3bf3a16.png" width="400px">

### コツ
+ カーソル位置に左右されないようなストロークにすること
 + 3文字消す`xxx` ではなく 次の空白まで消す`df ` とか
 + 繰り返す前に一度行頭に移動する`^` とか
+ 慣れないうちはfilterは`/`でハイライトされる箇所を見ながら組み立てると良い
+ mapの方も普通に操作して試してみれば良い

Vim力の向上と共に飛躍的にできることが増えてもう戻れなくなる！
やってみよー！

（思いつきでテキトー書いたわりに破綻はしなかった...かな？）