2

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.

Zeitounator
  • 38,476
  • 7
  • 53
  • 66
spark
  • 531
  • 6
  • 17

2 Answers2

2

Given the data

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

Q: "Transpose the matrix."

A: For example

    - set_fact:
        lst: "{{ lst|d(data.0)|zip(item)|map('flatten') }}"
      loop: "{{ data[1:] }}"

gives

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

The systemic way would be to create wrappers for Python NumPy package. For example, starting with numpy.matrix.transpose

shell> cat filter_plugins/numpy.py
import json
import numpy


def numpy_transpose(arr):
    arr1 = numpy.array(arr)
    arr2 = arr1.transpose()
    return json.dumps(arr2.tolist())


class FilterModule(object):
    ''' Ansible wrappers for Python NumPy methods '''

    def filters(self):
        return {
            'numpy_transpose': numpy_transpose,
        }

The declaration below gives the same result without iteration

  lst: "{{ data|numpy_transpose()|from_yaml }}"
Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
  • Thank you so much. I was gonna give this as answer as well!! Appreciate!!! – spark May 06 '22 at 17:30
  • BTW, what is the d(data.0) ? I never saw that which is pretty new to me. :-P Any reference link for me? – spark May 06 '22 at 17:47
  • *`d()`* is an alias for *`default()`*. The iteration starts by zipping the first two items *`data.0`* and *`data.1`* and proceeds by cumulatively zipping the next items (lists). See Jinja filter [*default*](https://jinja.palletsprojects.com/en/latest/templates/#jinja-filters.default) and Python [*Understanding slicing*](https://stackoverflow.com/questions/509211/understanding-slicing). – Vladimir Botka May 07 '22 at 04:06
1

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 
Zeitounator
  • 38,476
  • 7
  • 53
  • 66
  • Thank you so much for the quick answer. it helps me a lot!!! didn't know how to use first filter but now I do know. * unpacking operator I need to find more about it. yep, I will keep in mind that the name of variable as well. Appreciate!!! – spark May 06 '22 at 17:25