perlベースの静的サイトジェネレーターDapper入門： リストやハッシュを使ってデータを束ねる

# 要約

**Dapperによるサイトデータ構築においては、レイアウトファイルの中で変数を活用することになります。変数には、単一の値を代入できるだけでなく、複数のデータを「リスト」・「ハッシュ」という形で束ねて持たせることができます。
本記事ではリストとハッシュの概念・基本的な利用法について解説します。**

# Dapperおさらい

[前記事](https://qiita.com/doikoji/items/7ce1ce186b74bf9f4c75)では、レイアウトファイルの書き方、特に、穴埋めに使う「変数」の基本的な扱い方について解説しました。

Dapperでは、「型紙」にあたるレイアウトファイルに、記事ごとのパラメータを書き込んだ「ソースファイル」の内容を組み合わせて、公開用のhtmlファイルを作り出します。レイアウトファイルはところどころに「穴」が開けてあり、その穴を記事データファイルの内容で埋めていくわけです。

# Dapperではリスト・ハッシュが使える

前記事でみたのは、「変数」にたった一つの値（スカラー）が代入されている例だけでした。しかし、Dapperの変数には、複数のデータを束ねて格納することができます。母体たるperl言語同様、Dapperはリスト・ハッシュという2種類の束ね方をサポートしています。

<dl>
<dt>リスト</dt><dd>要素を一列に並べてまとめたものです。要素には順位が付けられており、「リストx内のn番目の要素」という形でリストの中の要素にアクセスできます。</dd>
<dt>ハッシュ</dt><dd>要素を「名前」と「値」の対としてまとめたものです。要素間の順位はありません。個々の要素には「ハッシュx内のnという名前をもつ値」という形でアクセスします。</dd>
</dl>
 
これらを使いこなせるようになると、束ねたデータに対して様々な一括処理ができるようになり、できることが大幅に増えます。

本記事では、リスト・ハッシュの取り扱いの基本について説明します。

# リスト

リストは`_config.yaml`の中では「シーケンス」として、レイアウトファイル中では以下の形式で定義できます。

```
[% a = [1, 2, 3, 4, 5] %]
[% a.1 %]
```
この例のように、リストはレイアウトファイル中の式ディレクティブの中で、`[]`の中に要素を列挙することで定義できます。リストの要素には0から始まる番号が付けられています。この番号を利用し、`変数名.番号`の書式で個々の要素を見ることができます。上の例では`a`の第1番要素すなわち`2`が出力されます。

リストは入れ子（ネスト）にすることもできます。

```
[% a = [1, 2, 3, 4, 5, ["a", "b", "c"]] %]
```

いわゆる表形式データ（二次元配列）を、perlやDapperの世界では、こうした「リストのリスト」で表現します。

```
[% prefecture = ["県名", "県庁所在地"], ["千葉", "千葉"], ["茨城", "水戸"]] %]
```

## リストの要素を参照する

リストの`n`番目の要素を参照するには、次のような書法が使えます。

```
[% a= [1,2,3,4,5] %]
[% a.1 %]
```
これで、`a`の1番目、すなわち`2`が出力されます。perlの流儀と同じく、リストの番号は0から始まります。

`.`の後ろには変数を置くこともできます。

```
[% a= [1,2,3,4,5] %]
[% b=1 %]
[% a.$b %]
```
しかし、多くの言語で普通に使われる`a[i+1]`のような参照はこの書法では対応できないようです。`[% a.$i+1 %]`などと書いても期待通りの結果は得られません。こうしたときは一時変数を使った継投策を講じるしかなさそうです。

```
[% a= [1,2,3,4,5] %]
[% b=1 %]
[% c=b+1 %]
[% a.$c %]
```

## リストに要素をひとつ加える

既存リストの**末尾**に要素を加えたいときは、関数`push`を使います。

```
[% a = [1, 2, 3, 4, 5] %]
[% a|push(10) %]
```
これを実行すると、リスト`a`の中身は、`[1, 2, 3, 4, 5, 10]`になります。関数`push`は、関数と言いながら、それ自体は意味のある値を返しません。ですから、次のような記述は良い結果をもたらしません（他ならぬ筆者が犯した間違いです）。

```
[% new_a = a|push(10) %]
```

しかし、参照対象のリスト（この場合は`a`）をしっかり書き換えてくれます。

一方、既存リストの**先頭**に要素を加えたいときは、関数`unshift`を使います。使い方は`push`と同じです。

```
[% a = [1, 2, 3, 4, 5] %]
[% a|unshift(10) %]
```

これを実行すると、リスト`a`の中身は、`[10, 1, 2, 3, 4, 5]`になります。

## リストから要素をひとつ取り出す

次のコードは、対象リストの**最初**の要素を取り除き、その値を返します。対象リストそのものが書き換えられてしまう点に注意。

```
[% a = [1, 2, 3, 4, 5] %]
[% a|shift %]
```

次のコードは、対象リストの**最後**の要素を取り除き、その値を返します。対象リストそのものが書き換えられてしまう点に注意。

```
[% a = [1, 2, 3, 4, 5] %]
[% a|pop %]
```

`push, pop, shift, unshift`などの関数はリストを「スタック」や「キュー」と捉えて利用する場合に有用です。perlプログラマならおなじみのスタイルですね。

参考：
[スタックとキューを極める！ 〜 考え方と使い所を特集 〜](https://qiita.com/drken/items/6a95b57d2e374a3d3292)

## リストの一部分をコピーする（元リストはそのまま）

```
[% b = a|slice(1,2) %]
```

## リストの一部分を切り出す（元リストから当該部分が削除される）

```
[% b = a|splice(1,2) %]
```

## slice,spliceの仕様の違いに注意

この二つの関数を使う時には注意が必要です。パラメータの意味に微妙な違いがあり、しかも公式ドキュメントの記述が実際と食い違っています（2020年2月現在）。

実例を見てみましょう。

```
[% a = [1,3,5,7,9,11] %]
splice:[% a|splice(2,3)|fmt %]
[% a = [1,3,5,7,9,11] %]
slice:[% a|slice(2,3)|fmt %]
```

この結果は、次のようになります。

```
splice:5 7 9

slice:5 7
```

このことからわかるとおり、問題は第2引数にあります。`splice`の第2引数は、「元リストから切り出される部分の最後尾の要素の番号」を意味しています。一方、`slice`の第2引数は、取り出すリストの大きさ（要素の数）となっています。つまり：

* `splice(a,b)`は、`a`番目から`b`番目までを取り出す。
* `slice(a,b)`は`a`番目から`b`この要素を取り出す。

公式ドキュメントではどちらも、`...beginning at index i and continuing for n items.`と説明されていますが、`splice`の仕様は実際にはそうなっていません。

## リストの中身をまとめて文字列にする

リスト内の各要素を整形し連結して一つの文字列にするフィルタとして、関数`fmt`が利用できます。

```
[% a = [1,3,5,7] %]
[% a|fmt %]
<br>
[% a|fmt("(%s)", ", ") %]
```

この結果は、次のようになります。

```
(1), (3), (5), (7)
```

`fmt`の第1引数には、`sprintf`に似た形式で各要素の整形スタイルを指定します。デフォルトは`%s`です。つまり要素の内容を「そのまま」一切の改変を加えずに文字列化します。第2引数には、「セパレータ」つまり要素同士を結合する際に結合部に挿入されるべき文字列を指定します。デフォルトはスペース1個です。

次の例は、リストの中身をhtmlの`<ol>`タグと`<li>`タグを使った番号付きリストとして出力します。

```
[% a = ["a","b","c","d"] %]
<ol>
[% a|fmt("<li>%s</li>", "") %]
</ol>
```

# ハッシュ

ハッシュは`_config.yaml`の中では「マッピング」として、レイアウトファイルでは以下の形式で定義できます。

```
[% link = {name => 'Vanilla Draft', 'url' => 'http://vanilladraft.com/'} %]
```

ハッシュ内の個々の要素は番号ではなくそれぞれにつけられた「名前」で特定されます。この名前のことを「キー」と呼びます。

## ハッシュに要素を加える

ハッシュに要素を追加するのは簡単です。

```
[% a = {A=>1, B=>2} %]
[% a.C = 100 %]
```
このようにごく普通にハッシュの要素を指定して値を代入してやればOKです。

## ハッシュから要素を取り除く

ハッシュから任意の要素を取り除くには、関数`delete`を使います。取り除きたい要素のキーを引数とします。

```
[% a|delete('A') %]
```

`delete`も、リストを操作するunshift, pushなどの関数と同様に、意味ある値を返さず対象のデータセットを直接改変するタイプの関数です。

## ハッシュの情報をいろいろと得る

### キーのリスト

特にforループの中でハッシュを扱いたい場合など、ハッシュのキーのリストが欲しくなるケースがあります。これには、関数`keys`が使えます。

```
[% a|keys %]
```

### 値のリスト

キーのリストが作れるなら、値のリストも当然作れると期待しますね。もちろん対応されています。関数`keys`と対を為す関数`values`を使いましょう。

```
[% a|values %]
```

### データの存在確認

あるキーをもったデータがハッシュ中に存在するか否か確認したいケースでは、関数`exists`が使えます。

```
[% a = {A=>1, B=>2} %]
[% a|exists('A') %]
```
`exists`の引数で指定したキーを持つ要素が対象ハッシュに含まれていた場合、`1`が返されます。含まれていない場合は空文字列が返されます。

次の例では、`exists`と[条件演算子](https://perldoc.jp/docs/perl/perlop.pod#Conditional32Operator)（三項演算子）による条件判定を組み合わせて出力をダイナミックに切り替えています。

```
[% a = {A=>1, B=>2} %]
キーAは、[% a|exists('A')==1 ? "存在する" : "存在しない" %]
キーCは、[% a|exists('C')==1 ? "存在する" : "存在しない" %]
```
出力はこうなります。

```
キーAは、存在する
キーCは、存在しない
```

# スカラー・リスト・ハッシュの相互変換

既に見てきたとおり、リストやハッシュをコンテンツの中で活用するには、最終的にそれらを単純文字列に変換するステップが不可欠です。

逆に、csvファイルなどからデータを読み込んで活かしたいときには、文字列として読み込んだデータをひとまずリストやハッシュに変換したうえで処理するのが有効です。

スカラー・リスト・ハッシュを相互に変換する方法はぜひ習得しておくべきです。以下に基本をまとめてみました。

## スカラー → リスト

コンマ区切りの文字列からリストを作る。頻発しそうなシチュエーションです。Dapperにはしかるべき関数がもちろん用意されています。

```
[% a = "A,T,C,G" %]
[% nuc = a|split(",") %]
<ul>
[% nuc|fmt("<li>%s</li>", "\n") %]
</ul>
```

これは次のような結果となります。

```
<ul>
<li>A</li>
<li>T</li>
<li>C</li>
<li>G</li>
</ul>
```

splitの第2引数には最大分割数（結果リストの最大要素数）を指定できます。
次は、結果リストの要素数上限を最大2と指定してみましょう。

```
[% a = "A,T,C,G" %]
[% nuc = a|split(",", 2) %]
<ul>
[% nuc|fmt("<li>%s</li>", "\n") %]
</ul>
```

結果は次の通りです。

```
<ul>
<li>A</li>
<li>T,C,G</li>
</ul>
```

## リスト → スカラー

関数`fmt`を使いましょう。

## リスト → ハッシュ

perlのハッシュの実体は、「キーと値の組が並んだリスト」です。Dapperもそのスタイルを踏襲しています。これを利用し、`キー１, 値１, キー２, 値２, ...`と並べたリストから、多メンバーからなるハッシュを一気に作り出すことができます。

```
[% mylist = ["a", "10", "b", "20"] %]
[% myhash = mylist|hash %]
a=[% myhash.a %]
b=[% myhash.b %]
```

## ハッシュ → リスト

キーごと・値ごとのリストを作る方法は、既に説明しました。

それらに比べて使い道は限られると思いますが、キーと値を混ぜてリストにすることもできます。関数`each`は、ハッシュを`キー１` `値１` `キー２` `値２` `...`と並べたリストに変換します。

```
[% myhash|each|fmt %]
```

# さらなる発展

リスト・ハッシュはforループと組み合わせることで、さらに威力を発揮することでしょう。

まず思いつくのは、`<ul>`や`<ol>`タグと組み合わせた箇条書きテキストの生成でしょう。ハッシュは`<dl><dt><dd>`の組み合わせで作る名前付きリストとも相性がよさそうです。

ループ制御については、別記事で詳しく述べる予定です。