はじめに
Ansibleで2つのリストを比較し、両方に含まれる要素やどちらかだけに含まれる要素を
抽出したいことがあると思います。その場合は、集合論フィルターが便利なので実際に使ってみました。
TL;DR
-
Ansibleでは以下の集合論フィルターが使用可能。リストだけでなく、辞書のリストに対しても使用可能。
集合論フィルター 演算子 説明 記載例 unique - 重複を排除 {{ list_A | unique }} intersect A ∩ B 積集合(AかつB) {{ list_A | intersect(list_B) }} union A ∪ B 和集合(AまたはB)
※AかつBも含まれる{{ list_A | union(list_B) }} symmetric_difference A xor B 排他的論理和(AのみまたはBのみ)
※AかつBは含まれない{{ list_A | symmetric_difference(list_B) }} difference A ー B 差集合(AにはあるがBにはない) {{ list_A | difference(list_B) }} -
辞書のリスト同士を比較した際に同じ要素とみなされるには、辞書内のすべてのキーと値が一致している必要がある。
-
集合論フィルターを活用することで、辞書のリスト同士の比較が容易になる。
前提条件
- Ansibleにおけるフィルターの使い方を理解していること
実行環境
- Python: 3.9.16
- Ansible: 2.9.7
- Jinja2: 3.0.3
詳細
1. リストに対して集合論フィルターを使用
1-1. 実行するPlaybook
- Playbookでは、以下2つのことを確認します
- 変数
list_unique
に対し、リストの重複が排除されること(unique) - 変数
list_A
とlist_B
に対し、正しい演算が行われること(intersect/union/symmetric_difference/difference)
- 変数
---
- name: "List set theory filters"
hosts: localhost
vars:
list_unique: [1, 2, 3, 2, 3, 4] # 2と3が重複
list_A: [1, 2, 3, 4] # 3と4が重複
list_B: [3, 4, 5, 6]
gather_facts: false
tasks:
- name: Test unique
debug:
msg: "{{ list_unique | unique }}"
- name: Test intersect
debug:
msg: "{{ list_A | intersect(list_B) }}"
- name: Test union
debug:
msg: "{{ list_A | union(list_B) }}"
- name: Test symmetric_difference
debug:
msg: "{{ list_A | symmetric_difference(list_B) }}"
- name: Test difference
debug:
msg: "{{ list_A | difference(list_B) }}"
1-2. 実行結果
想定通りの結果を得ることができました。
$ ansible-playbook list_set_theory_filters.yml
PLAY [List set theory filters] *******************************************************************
TASK [Test unique] *******************************************************************************
ok: [localhost] =>
msg:
- 1
- 2
- 3
- 4
TASK [Test intersect] ****************************************************************************
ok: [localhost] =>
msg:
- 3
- 4
TASK [Test union] ********************************************************************************
ok: [localhost] =>
msg:
- 1
- 2
- 3
- 4
- 5
- 6
TASK [Test symmetric_difference] *****************************************************************
ok: [localhost] =>
msg:
- 1
- 2
- 5
- 6
TASK [Test difference] ***************************************************************************
ok: [localhost] =>
msg:
- 1
- 2
PLAY RECAP ***************************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
2. 辞書のリストに対して集合論フィルターを使用
2-1. 実行するPlaybook
- Playbookでは1つ前と同様に、以下2つのことを確認します
- 変数
list_unique
に対し、リストの重複が排除されること(unique) - 変数
list_A
とlist_B
に対し、正しい演算が行われること(intersect/union/symmetric_difference/difference)
- 変数
---
- name: "Dict set theory filters"
hosts: localhost
vars:
list_unique:
- {id: 1, name: "test 1"}
- {id: 2, name: "test 2"}
- {id: 3, name: "test 3"}
- {id: 2, name: "test 2"}
- {id: 3, name: "test 3"}
- {id: 4, name: "test 4"}
list_A:
- {id: 1, name: "test 1"}
- {id: 2, name: "test 2"}
- {id: 3, name: "test 3"}
- {id: 4, name: "test 4"}
list_B:
- {id: 3, name: "test 3"}
- {id: 4, name: "test 4"}
- {id: 5, name: "test 5"}
- {id: 6, name: "test 6"}
gather_facts: false
tasks:
- name: Test unique
debug:
msg: "{{ list_unique | unique }}"
- name: Test intersect
debug:
msg: "{{ list_A | intersect(list_B) }}"
- name: Test union
debug:
msg: "{{ list_A | union(list_B) }}"
- name: Test symmetric_difference
debug:
msg: "{{ list_A | symmetric_difference(list_B) }}"
- name: Test difference
debug:
msg: "{{ list_A | difference(list_B) }}"
2-2. 実行結果
辞書のリストに対しても、想定通りの結果を得ることができました。
$ ansible-playbook dict_set_theory_filters.yml
PLAY [Dict set theory filters] **********************************************************
TASK [Test unique] **********************************************************************
ok: [localhost] =>
msg:
- id: 1
name: test 1
- id: 2
name: test 2
- id: 3
name: test 3
- id: 4
name: test 4
TASK [Test intersect] *******************************************************************
ok: [localhost] =>
msg:
- id: 3
name: test 3
- id: 4
name: test 4
TASK [Test union] ***********************************************************************
ok: [localhost] =>
msg:
- id: 1
name: test 1
- id: 2
name: test 2
- id: 3
name: test 3
- id: 4
name: test 4
- id: 5
name: test 5
- id: 6
name: test 6
TASK [Test symmetric_difference] ********************************************************
ok: [localhost] =>
msg:
- id: 1
name: test 1
- id: 2
name: test 2
- id: 5
name: test 5
- id: 6
name: test 6
TASK [Test difference] ******************************************************************
ok: [localhost] =>
msg:
- id: 1
name: test 1
- id: 2
name: test 2
PLAY RECAP ******************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
2-3. 注意点
- 辞書のリスト同士を比較した際に同じ要素とみなされるには、辞書内のすべてのキーと値が一致している必要があります。
-
例1)intersectフィルターで
id
とname
の値は一致しているが、list_B
にのみキーhoge
があるので同じとみなされない-
タスク
- name: Test intersect debug: msg: "{{ list_A | intersect(list_B) }}" vars: list_A: - {id: 1, name: "test 1"} list_B: - {id: 1, name: "test 1", hoge: "hoge"}
-
実行結果
TASK [Test intersect] *************************************************************** ok: [localhost] => msg: []
-
-
例2)intersectフィルターで両方にキー
id
とname
はあるが、キーname
の値が異なるので同じとみなされない-
タスク
- name: Test intersect debug: msg: "{{ list_A | intersect(list_B) }}" vars: list_A: - {id: 1, name: "test 1"} list_B: - {id: 1, name: "hoge"}
-
結果
TASK [Test intersect] *************************************************************** ok: [localhost] => msg: []
-
-
3. 実践例
集合論フィルターを活用することで、辞書のリスト同士の比較が容易になります。
3-1. list_Aとlist_Bに重複がないことの確認
list_A
がlist_B
に重複がないことを確認する場合の例が以下です。例では、list_A
とlist_B
の両方に1
があるのでエラーになります。
-
タスク
- name: Check that there are no duplicates in list_A and list_B assert: that: - (list_A | intersect(list_B)) | length == 0 fail_msg: - "list_A and list_B: {{ list_A | intersect(list_B) }}" - "Only list_A: {{ list_A | difference(list_B) }}" - "Only list_B: {{ list_B | difference(list_A) }}" vars: list_A: [1] list_B: [1, 2]
-
実行結果
TASK [Check that there are no duplicates in list_A and list_B] ********************** fatal: [localhost]: FAILED! => changed=false assertion: (list_A | intersect(list_B)) | length == 0 evaluated_to: false msg: - 'list_A and list_B: [1]' - 'Only list_A: []' - 'Only list_B: [2]'
3-2. list_Aがlist_Bに含まれることの確認(A⊂B)
list_A
がlist_B
に含まれることを確認する場合の例が以下です。例では、list_A
に5
がに含まれているのでエラーになります。
-
タスク
- name: Check that list_A is in list_B assert: that: - (list_A | intersect(list_B)) == list_A fail_msg: - "list_A and list_B: {{ list_A | intersect(list_B) }}" - "Only list_A: {{ list_A | difference(list_B) }}" - "Only list_B: {{ list_B | difference(list_A) }}" vars: list_A: [1, 2, 5] list_B: [1, 2, 3, 4]
-
実行結果
TASK [Check that list_A is in list_B] *********************************** fatal: [localhost]: FAILED! => changed=false assertion: (list_A | intersect(list_B)) == list_A evaluated_to: false msg: - 'list_A and list_B: [1, 2]' - 'Only list_A: [5]' - 'Only list_B: [3, 4]'
さいごに
- 集合論フィルターを活用することで、辞書のリスト同士の比較が容易になるので積極的に活用しましょう。
- 集合論フィルターの使い方を整理しながら、高校数学の「集合と論理」を思い出しました。懐かしいですね。
参考URL
-
Ansible Documentation - Filters
- 集合論フィルターの使い方