Ansibleの呼吸 参ノ型 フィルター芸「リスト内にある重複している辞書の数を確認」

この投稿は[Ansible 3 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ansible3)の4日目の記事です。

リスト内にある重複している辞書の数を確認する時のフィルター例です。
<font color="#FC0107">ここではシーケンスをリスト、マッピングを辞書と表現します</font>

# Playbook

Playbookはこんな感じです。

```yaml
---
- name: san no kata playbook
  hosts: localhost
  gather_facts: no
  vars:
    damon_slayer:
      - name: 竈門炭治郎
        type: 水
        partner: 天王寺松衛門

      - name: 嘴平伊之助
        type: 獣
        partner: ''

      - name: 我妻善逸
        type: 雷
        partner: チュン太郎

      - name: 竈門炭治郎
        type: 水
        partner: 天王寺松衛門

      - name: 竈門炭治郎
        type: 水
        partner: 天王寺松衛門

      - name: 嘴平伊之助
        type: 獣
        partner: ''
  tasks:
    - name: set list1 variable
      set_fact:
        list1: >-
          {{ list1 | default([])
            + [ damon_slayer
               | selectattr("name", "equalto", item.name)
               | list
              ]
          }}
      loop: "{{ damon_slayer }}"

    - debug:
        msg: >-
          {{ item.0 }} : {{ count }}
      vars:
        count: "{{ item | length }}"
      loop: "{{ list1 | unique }}"
      loop_control:
        label: "{{ item.0.name }}"
```

ここでは `name` キーがマッチした場合に `list1` にまとめて、最後に `unique` して `loop` で回し数をカウントした結果を表示します。

# Playbook実行

これを実行します。

```
(venv) [root@localhost ansible]# ansible-playbook main.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'


PLAY [san no kata playbook] *****************************************************************************************************************************************************

TASK [set list1 variable] *******************************************************************************************************************************************************
ok: [localhost] => (item={'name': '竈門炭治郎', 'type': '水', 'partner': '天王寺松衛門'})
ok: [localhost] => (item={'name': '嘴平伊之助', 'type': '獣', 'partner': ''})
ok: [localhost] => (item={'name': '我妻善逸', 'type': '雷', 'partner': 'チュン太郎'})
ok: [localhost] => (item={'name': '竈門炭治郎', 'type': '水', 'partner': '天王寺松衛門'})
ok: [localhost] => (item={'name': '竈門炭治郎', 'type': '水', 'partner': '天王寺松衛門'})
ok: [localhost] => (item={'name': '嘴平伊之助', 'type': '獣', 'partner': ''})

TASK [debug] ********************************************************************************************************************************************************************
ok: [localhost] => (item=竈門炭治郎) => {
    "msg": "{'name': '竈門炭治郎', 'type': '水', 'partner': '天王寺松衛門'} : 3"
}
ok: [localhost] => (item=嘴平伊之助) => {
    "msg": "{'name': '嘴平伊之助', 'type': '獣', 'partner': ''} : 2"
}
ok: [localhost] => (item=我妻善逸) => {
    "msg": "{'name': '我妻善逸', 'type': '雷', 'partner': 'チュン太郎'} : 1"
```

こんな感じでカウントできます。
例えば他のキーのand条件を取りたい場合は以下のように `selectattr` を増やします。

```yaml
---
- name: san no kata playbook
  hosts: localhost
  gather_facts: no
  vars:
    damon_slayer:
      - name: 竈門炭治郎
        type: 水
        partner: 天王寺松衛門

      - name: 嘴平伊之助
        type: 獣
        partner: ''

      - name: 我妻善逸
        type: 雷
        partner: チュン太郎

      - name: 竈門炭治郎
        type: 水
        partner: 天王寺松衛門

      - name: 竈門炭治郎
        type: 水
        partner: 天王寺松衛門

      - name: 竈門炭治郎
        type: 火
        partner: 天王寺松衛門

      - name: 嘴平伊之助
        type: 獣
        partner: ''
  tasks:
    - name: set list1 variable
      set_fact:
        list1: >-
          {{ list1 | default([])
            + [ damon_slayer
               | selectattr("name", "equalto", item.name)
               | selectattr("type", "equalto", item.type)
               | list
              ]
          }}
      loop: "{{ damon_slayer }}"

    - debug:
        msg: >-
          {{ item.0 }} : {{ count }}
      vars:
        count: "{{ item | length }}"
      loop: "{{ list1 | unique }}"
      loop_control:
        label: "{{ item.0.name }}"
```

実行してみます。

```
(venv) [root@localhost ansible]# ansible-playbook main.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'


PLAY [san no kata playbook] *****************************************************************************************************************************************************

TASK [set list1 variable] *******************************************************************************************************************************************************
ok: [localhost] => (item={'name': '竈門炭治郎', 'type': '水', 'partner': '天王寺松衛門'})
ok: [localhost] => (item={'name': '嘴平伊之助', 'type': '獣', 'partner': ''})
ok: [localhost] => (item={'name': '我妻善逸', 'type': '雷', 'partner': 'チュン太郎'})
ok: [localhost] => (item={'name': '竈門炭治郎', 'type': '水', 'partner': '天王寺松衛門'})
ok: [localhost] => (item={'name': '竈門炭治郎', 'type': '水', 'partner': '天王寺松衛門'})
ok: [localhost] => (item={'name': '竈門炭治郎', 'type': '火', 'partner': '天王寺松衛門'})
ok: [localhost] => (item={'name': '嘴平伊之助', 'type': '獣', 'partner': ''})

TASK [debug] ********************************************************************************************************************************************************************
ok: [localhost] => (item=竈門炭治郎) => {
    "msg": "{'name': '竈門炭治郎', 'type': '水', 'partner': '天王寺松衛門'} : 3"
}
ok: [localhost] => (item=嘴平伊之助) => {
    "msg": "{'name': '嘴平伊之助', 'type': '獣', 'partner': ''} : 2"
}
ok: [localhost] => (item=我妻善逸) => {
    "msg": "{'name': '我妻善逸', 'type': '雷', 'partner': 'チュン太郎'} : 1"
}
ok: [localhost] => (item=竈門炭治郎) => {
    "msg": "{'name': '竈門炭治郎', 'type': '火', 'partner': '天王寺松衛門'} : 1"
}

PLAY RECAP **********************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

重複している辞書の数が確認できました :)