Search code examples
ansiblejinja2ansible-factsjson-query

How can I alternate the elements of multiple lists in Ansible?


I have multiple lists as input (all lists have the same length, but the input can have more than 3 lists). I want to create a list which is a sum of all input lists alternating their elements.

For example, given the following input:

data:
  - ['1','2','3','4','5']
  - ['6','7','8','9','10']
  - ['11','12','13','14','15']

I'm expecting the following output:

lst: [['1','6','11'],['2','7','12'],['3','8','13'],['4','9','14'],['5','10','15']]

This is what I've tried:


---
- name: zip more than 3 lists with loop
  hosts: localhost
  tasks:
    - name: Set facts
      set_fact:
        list:
          - ['1','2','3','4','5']
          - ['6','7','8','9','10']
          - ['11','12','13','14','15']

    - name: zip to make pairs of both lists
      set_fact:
        lst: "{{ list[0] | zip(list[1]) | zip(list[2]) | list }}"

    - name: Debug ['1','6','11'],['2','7','13'],...
      debug:
        msg: "{{ item | flatten }}"
      loop: "{{ lst }}"

    - name: zip to make pairs of both lists
      set_fact:
        lst2: "{{ lst2 | default([]) | zip(ansible_loop.nextitem) | list }}"
      loop: "{{ list }}"
      loop_control:
        extended: yes

    - name: Debug
      debug:
        msg: "{{ lst2 }}"

The first set_fact outputs loop elements but lst doesn't include the actual output I expect. And the limitation of the first set_fact is that I can't iterate in the loop due to zip filter. I don't know how to acheive my goal.


Solution

  • Preliminary note: list being the name of a jinja2 filter, I very strongly suggest you do not use it as a variable name


    Ansible being mainly python compatible, you can take advantage of the * unpacking operator to turn your list elements into arguments to the function/filter you are calling which is accepting a variable number of arguments (as zip or lookup below).

    The following playbook will work with any number of lists in data_list (as far as this number is stricly superior to 1...)

    ---
    - name: zip more than 3 lists with loop
      hosts: localhost
    
      vars:
        data_list:
          - ['1','2','3','4','5']
          - ['6','7','8','9','10']
          - ['11','12','13','14','15']
    
        # You can do this with your original zip tentative
        alternated_list1: "{{ (data_list | first) | zip(*data_list[1:]) }}"
    
        # But I find it more elegant with the together lookup here
        alternated_list2: "{{ lookup('together', *data_list) }}"
    
      tasks:
        - name: calculated with zip
          debug:
            var: alternated_list1
    
        - name: calculated with together lookup
          debug:
            var: alternated_list2
    
        - name: And of course you can use the result, for example in a loop
          debug:
            var: item
          loop: "{{ alternated_list2 }}"
    

    and gives:

    PLAY [zip more than 3 lists with loop] ********************************************************************************************************************************
    
    TASK [Gathering Facts] ************************************************************************************************************************************************
    ok: [localhost]
    
    TASK [calculated with zip] ********************************************************************************************************************************************
    ok: [localhost] => {
        "alternated_list1": [
            [
                "1",
                "6",
                "11"
            ],
            [
                "2",
                "7",
                "12"
            ],
            [
                "3",
                "8",
                "13"
            ],
            [
                "4",
                "9",
                "14"
            ],
            [
                "5",
                "10",
                "15"
            ]
        ]
    }
    
    TASK [calculated with together lookup] ********************************************************************************************************************************
    ok: [localhost] => {
        "alternated_list2": [
            [
                "1",
                "6",
                "11"
            ],
            [
                "2",
                "7",
                "12"
            ],
            [
                "3",
                "8",
                "13"
            ],
            [
                "4",
                "9",
                "14"
            ],
            [
                "5",
                "10",
                "15"
            ]
        ]
    }
    
    TASK [And of course you can use the result, for example in a loop] ****************************************************************************************************
    ok: [localhost] => (item=['1', '6', '11']) => {
        "ansible_loop_var": "item",
        "item": [
            "1",
            "6",
            "11"
        ]
    }
    ok: [localhost] => (item=['2', '7', '12']) => {
        "ansible_loop_var": "item",
        "item": [
            "2",
            "7",
            "12"
        ]
    }
    ok: [localhost] => (item=['3', '8', '13']) => {
        "ansible_loop_var": "item",
        "item": [
            "3",
            "8",
            "13"
        ]
    }
    ok: [localhost] => (item=['4', '9', '14']) => {
        "ansible_loop_var": "item",
        "item": [
            "4",
            "9",
            "14"
        ]
    }
    ok: [localhost] => (item=['5', '10', '15']) => {
        "ansible_loop_var": "item",
        "item": [
            "5",
            "10",
            "15"
        ]
    }
    
    PLAY RECAP ************************************************************************************************************************************************************
    localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0