Notes
- Example of a complete playbook
- hosts: localhost
  vars:
    my_dict:
      key1: ["111", "222"]
      key2: ["444", "555"]
    keys: "{{ my_dict.keys()|list }}"
    vals: "{{ my_dict.values()|list }}"
    my_list: "{{ tvals|map('zip', keys)|
                       map('map', 'reverse')|
                       map('community.general.dict')|
                       list }}"
  tasks:
    - set_fact:
        tvals: "{{ tvals|d(vals.0)|zip(item)|map('flatten') }}"
      loop: "{{ vals[1:] }}"
    - debug:
        var: my_list
- You can use a custom filer to transpose the matrix. For example,
shell> cat filter_plugins/numpy.py 
# All rights reserved (c) 2022, Vladimir Botka <vbotka@gmail.com>
# Simplified BSD License, https://opensource.org/licenses/BSD-2-Clause
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common._collections_compat import Sequence
import json
import numpy
def numpy_transpose(arr):
    if not isinstance(arr, Sequence):
        raise AnsibleFilterError('First argument for numpy_transpose must be list. %s is %s' %
                                 (arr, type(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,
        }
Then you can avoid iteration. For example, the playbook below gives the same result
- hosts: localhost
  vars:
    my_dict:
      key1: ["111", "222"]
      key2: ["444", "555"]
    keys: "{{ my_dict.keys()|list }}"
    vals: "{{ my_dict.values()|list }}"
    tvals: "{{ vals|numpy_transpose()|from_yaml }}"
    my_list: "{{ tvals|map('zip', keys)|
                       map('map', 'reverse')|
                       map('community.general.dict')|
                       list }}"
  tasks:
    - debug:
        var: my_list
- Transposing explained
Let's start with the matrix 2x2
vals:
  - ['111', '222']
  - ['444', '555']
The task below
    - set_fact:
        tvals: "{{ tvals|d(vals.0)|zip(item) }}"
      loop: "{{ vals[1:] }}"
gives step by step:
a) Before the iteration starts the variable tvals is assigned the default value vals.0
vals.0: ['111', '222']
b) The task iterates the list vals[1:]. These are all lines in the array except the first one
vals[1:]:
  - ['444', '555']
c) The first, and the only one, iteration zip the first and the second line. This is the result
vals.0|zip(vals.1):
  - ['111', '444']
  - ['222', '555']
Let's proceed with matrix 3x3
vals:
  - ['111', '222', '333']
  - ['444', '555', '666']
  - ['777', '888', '999']
The task below
    - set_fact:
        tvals: "{{ tvals|d(vals.0)|zip(item)|map('flatten') }}"
      loop: "{{ vals[1:] }}"
gives step by step:
a) Before the iteration starts the variable tvals is assigned the default value vals.0
vals.0: ['111', '222', '333']
b) The task iterates the list vals[1:]
vals[1:]:
  - ['444', '555', '666']
  - ['777', '888', '999']
c) The first iteration zip the first and the second line, and assigns it to tvals. The filer flatten has no effect on the lines
vals.0|zip(vals.1)|map('flatten'):
  - ['111', '444']
  - ['222', '555']
  - ['333', '666']
d) The next iteration zip tvals and the third line
  tvals|zip(vals.2):
    - - ['111', '444']
      - '777'
    - - ['222', '555']
      - '888'
    - - ['333', '666']
      - '999
e) The lines must be flattened. This is the result
tvals|zip(vals.2)|map('flatten'):
  - ['111', '444', '777']
  - ['222', '555', '888']
  - ['333', '666', '999']