'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 1:[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
Solution 2:[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 }}"
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |