Search code examples
ansiblejinja2

How to compare more than two lists have the same values in same indexes in Ansible?


I would like to compare more than 2 lists, and see if they have the same values in same indexes.

I have the previous questions regarding how I can alternate the values in the multiple lists. here Based on this, I need to compare each values and stop if there is value non-identical. (There might be better solution, please welcome to add)

---
- name: data test
  hosts: localhost
  vars:
    data_1: [True, False, True]
    data_2: [False, True, True]
    
  tasks:
  - name: combine
    set_fact:
      comp_data: "{{ comp_data | default([]) + [item] }}"
    loop: 
      - "{{ data_1 }}"
      - "{{ data_2 }}"

  - name: list all comp_data
    debug:
      msg: "{{ comp_data }}"

  - name: zip
    set_fact:
      zip_data: "{{ lookup('together', *comp_data) }}"

  - name: list all zip comp_data
    debug:
      msg: "{{ zip_data }}"
    
  - name: check each element are identical
    assert:
      that:
        - "{{ item | unique | length == 1 }}"
      msg: |
        "Data is not identical"
        "{{ item }}"
    loop: "{{ zip_data }}"

Output:

TASK [check each element are identical] *************************************************************************************************************************************************
failed: [localhost] (item=[True, False]) => {
    "ansible_loop_var": "item",
    "assertion": false,
    "changed": false,
    "evaluated_to": false,
    "item": [
        true,
        false
    ]
}

MSG:

"Data is not identical"
"[True, False]"

failed: [localhost] (item=[False, True]) => {
    "ansible_loop_var": "item",
    "assertion": false,
    "changed": false,
    "evaluated_to": false,
    "item": [
        false,
        true
    ]
}

MSG:

"Data is not identical"
"[False, True]"

ok: [localhost] => (item=[True, True]) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": [
        true,
        true
    ]
}

MSG:

All assertions passed

At the end, it should stop as soon as it sees non-identical, but it iterates until the last list.

Any idea how I can stop right away and better way to compare lists if any?

Thank you,


Solution

  • Q: "Stop as soon as it sees non-identical items."

    A: Find the index and slice the lists. For example, given the data

    data_1: [True, False, True]
    data_2: [False, True, True]
    

    Find the index where the lists start to differ

    data_diff: "{{ data_1|zip(data_2)|
                   map('unique')|
                   map('length')|
                   map('log', 100)|map('round', 0, method='ceil')|map('int')|
                   list }}"
    data_stop_index: |-
      {% if 1 in data_diff %}{{ data_diff.index(1) }}
      {% else %}{{ data_diff|length }}
      {% endif %}
    

    gives

    data_diff: [1, 1, 0]
    data_stop_index: 0
    

    Then, the iteration

        - debug:
            msg: "{{ item|to_yaml }}"
          with_together:
            - "{{ data_1[:data_stop_index|int] }}"
            - "{{ data_2[:data_stop_index|int] }}"
    

    gives no result because the first items are different


    When you change the data

        data_1: [True, False, True]
        data_2: [True, True, True]
    

    the above task will display the first items

      msg: |-
        [true, true]
    

    The code can be modified to process more lists. For example, given the data

    data_1: [A, B, C]
    data_2: [A, X, C]
    data_3: [A, B, C]
    

    zip and flatten up to 100 lists (increase the logarithm base if you want more)

    data_diff: "{{ data_1|zip(data_2)|zip(data_3)|
                   map('flatten')|
                   map('unique')|
                   map('length')|
                   map('log', 100)|map('round', 0, method='ceil')|map('int')|
                   list }}"
    data_stop_index: |-
      {% if 1 in data_diff %}{{ data_diff.index(1) }}
      {% else %}{{ data_diff|length }}
      {% endif %}
    

    gives

    data_diff: [0, 1, 0]
    data_stop_index: 1
    

    Then, slice and iterate together all lists

        - debug:
            msg: "{{ item|to_yaml }}"
          with_together:
            - "{{ data_1[:data_stop_index|int] }}"
            - "{{ data_2[:data_stop_index|int] }}"
            - "{{ data_3[:data_stop_index|int] }}"
    

    gives the first items

      msg: |-
        [A, A, A]
    

    Optionally, create a custom filter to compare the items in a list

    shell> cat filter_plugins/bool.py 
    def bool_gt(a, b):
        return (a > b)
    
    
    class FilterModule(object):
        ''' Ansible filters for operating on Boolean '''
    
        def filters(self):
            return {
                'bool_gt': bool_gt,
            }
    

    Then, use it instead of the awkward built-in filters' pipe (log, round, int)

    data_diff: "{{ data_1|zip(data_2)|zip(data_3)|
                   map('flatten')|
                   map('unique')|
                   map('length')|
                   map('bool_gt', 1)|
                   list }}"
    

    and fit the calculation of the index

    data_stop_index: |-
      {% if true in data_diff %}
      {{ data_diff.index(true) }}
      {% else %}
      {{ data_diff|length }}
      {% endif %}