How to map list using value from another list 1 by 1 in Elixir? - functional-programming

Suppose I have a Player Struct inside list
[
%Player{name: "John", role: "nil"},
%Player{name: "Sansa", role: "nil"},
%Player{name: "Barry", role: "nil"},
%Player{name: "Edward", role: "nil"}
]
and I have a list of roles:
Enum.shuffle([:werewolf, :farmer, :farmer, :farmer])
What function to use || How do I map it one by one into my expected result:
[
%Player{name: "John", role: ":farmer"},
%Player{name: "Sansa", role: ":farmer"},
%Player{name: "Barry", role: ":werewolf"},
%Player{name: "Edward", role: ":farmer"}
]
I tried mapping, but with OO background, all I think is matching the index, which is not efficient in Elixir.

In general, when you want to somehow work with two lists on the same size and do something element-wise, Enum.zip/2 or Enum.zip_with/3 are a common and efficient way to achieve it without relying on index access.
Your example can be solved by:
Enum.zip_with(players, roles, fn player, role -> Map.put(player, :role, role) end)

You could take a different approach: make them all farmers, and randomly assign one as the warewolf:
defmodule Player do
defstruct name: nil, role: :farmer
def setup do
[werewolf | farmers] =
["John", "Sansa", "Barry", "Edward"]
|> Enum.map(fn name -> %Player{name: name} end)
|> Enum.shuffle()
[%Player{werewolf | role: :werewolf} | farmers]
end
end
This will return the players in a random order, though (and the werewolf will always be first).

Related

ansible json_query to with dicts containing key values as list

I have a below value in a variable allcsv
"msg": [
{
"added_bundle_images": [
"locuz.qe.pnq.local/eng-build/lzfirewallmod:v4.8.3-10"
],
"index_image": "locuz.qe.pnq.local/eng-build/iib:297699",
"locuz_version": "v4.8"
},
{
"added_bundle_images": [
"locuz.qe.pnq.local/eng-build/lzfirewallmod:v4.10.0-57"
],
"index_image": "locuz.qe.pnq.local/eng-build/iib:297697",
"locuz_version": "v4.9"
},
{
"added_bundle_images": [
"locuz.qe.pnq.local/eng-build/lzfirewallmod:v4.7.7-14"
],
"index_image": "locuz.qe.pnq.local/eng-build/iib:297497",
"locuz_version": "v4.7"
},
{
"added_bundle_images": [
"locuz.qe.pnq.local/eng-build/lzfirewallmod:v4.9.2-3"
],
"index_image": "locuz.qe.pnq.local/eng-build/iib:297495",
"locuz_version": "v4.9"
}
]
I have a ansible variable which contains one of the values from added_bundle_images. for example i have a variable called lastcsv which contains value "locuz.qe.pnq.local/eng-build/lzfirewallmod:v4.7.7-14"
I am trying to write a json_query and also trying selectattr to get index_image if my key value matches "locuz.qe.pnq.local/eng-build/lzfirewallmod:v4.7.7-14"
- name: set my variable
set_fact:
lastcsv: "locuz.qe.pnq.local/eng-build/lzfirewallmod:v4.7.7-14"
- name: my debug2
debug: msg="{{ allcsv | selectattr('added_bundle_images','equalto', [lastcsv]) | list }}"
The output i see is :
TASK [common : my debug2] ***************************************************************************************************************************************************
ok: [foo.example.com] => {
"msg": []
}
Need help in constructing the query so that i get the index_image value when the added_bundle_image key has value "locuz.qe.pnq.local/eng-build/lzfirewallmod:v4.7.7-14"
In allcsv, added_bundle_images is a list (denoted by []), so we can use contains match rather than equalto to see if the lastcsv element is in that list. Something like below:
- name: set my variable
set_fact:
lastcsv: "locuz.qe.pnq.local/eng-build/lzfirewallmod:v4.7.7-14"
- name: my debug2
debug:
msg: "{{ allcsv | selectattr('added_bundle_images', 'contains', lastcsv) | map(attribute='index_image') | list }}"
Produces:
TASK [my debug2] ********************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"locuz.qe.pnq.local/eng-build/iib:297497"
]
}

HOT template for cinder volume with or without volume_type

I am trying to write a HOT template for Openstack volume, and need to have the volume_type as a parameter. I also need to support a case when the parameter is not given, and default to the Cinder default volume type.
First attempt was to pass null to the volume_type , hoping it would give the default volume type. However no matter what I pass (null, ~, default, "" ) , seems there is no way to get the default volume type.
type: OS::Cinder::Volume
properties:
name: test
size: 1
volume_type: { if: ["voltype_given" , {get_param:[typename]} , null] }
Is there any way to get the default volume type , when you have the "volume_type" property defined?
Alternatively, is there any way to have the "volume_type" property itself behind a conditional? I tried several ways, but no luck. Something like:
type: OS::Cinder::Volume
properties:
if: ["voltype_given" , [ volume_type: {get_param:[typename]} ] , ""]
name: test
size: 1
ERROR: TypeError: : resources.kk-test-vol: : 'If' object is not iterable
Could you do something like this?
---
parameters:
typename:
type: string
conditions:
use_default_type: {equals: [{get_param: typename}, '']}
resources:
MyVolumeWithDefault:
condition: use_default_type
type: OS::Cinder::Volume
properties:
name: test
size: 1
MyVolumeWithExplicit:
condition: {not: use_default_type}
type: OS::Cinder::Volume
properties:
name: test
size: 1
volume_type: {get_param: typename}
# e.g. if you need to refer to the volume from another resource
MyVolumeAttachment:
type: OS::Cinder::VolumeAttachment
properties:
instance_uid: some-instance-uuid
volume_id:
if:
- use_default_type
- get_resource: MyVolumeWithDefault
- get_resource: MyVolumeWithExplicit

Formatting set_fact variable

I need to have all the userid in a single variable, all separated by \n.
Code is as below.
- name: Retrieve the user id and instance
shell: ls -l {{item}} | grep -v total| awk '{print $3}'
register: find_result_userid
with_items:
- /tmp/log/logs/log1
- /tmp/log/logs/log2
- /tmp/log/logs/log3
- name: Combine all userid
set_fact:
server_names: "{{ find_result_userid.results | map(attribute='stdout_lines')|list }}"
The output is as below.
ok: [localhost] => {
"ansible_facts": {
"server_names": [
[
"root",
"root",
"root"
],
[
"root",
"root",
"root"
],
[
"root",
"root",
"root"
]
]
},
"changed": false
}
I need something like below: i.e all ids separated by a line in a single variable.
"server_names": [
[
"root",
"root",
"root",
"root",
"root",
"root",
"root",
"root",
"root"
]
Kindly advise.
flatten the lists
- set_fact:
server_names: "{{ server_names|flatten }}"
If the number of items you are iterating with is static, I would guess, that you can use the + operator to append the your results
- name: Combine all userid
set_fact:
server_names: "{{ find_result_userid.results[0].stdout_lines + find_result_userid.results[1].stdout_lines + find_result_userid.results[2].stdout_lines}}"
Otherwise if it is not static, I think Vladimir Botkas answer is better.
Combining with what Vladmir Botka suggested, to get result in a single task.
- name: Combine all userid
set_fact:
server_names: "{{ find_result_userid.results | map(attribute='stdout_lines')|list | flatten }}"

ansible flattened map filter results

I'm using Ansible's map filter to extract data, but the output is a list of lists; what I need is a flattened list. The closest I've come is illustrated by the "energy.yml" playbook below. Invoke as
ansible-playbook ./energy.yml --extra-vars='src=solar'
---
- hosts: localhost
vars:
region: [ 'east', 'west' ]
sources:
wind:
east:
filenames:
- noreaster.txt
- gusts.txt
- drafty.txt
west:
filenames:
- zephyr.txt
- jetstream.txt
solar:
east:
filenames:
- sunny.txt
- cloudy.txt
west:
filenames:
- blazing.txt
- frybaby.txt
- skynuke.txt
src: wind
tasks:
- name: Do the {{ src }} data
debug:
msg: "tweak file '/energy/{{src}}/{{ item[0] }}/{{ item[1] }}'."
with_nested:
- "{{ region }}"
- "{{
(region|map('extract',sources[src],'filenames')|list)[0] +
(region|map('extract',sources[src],'filenames')|list)[1]
}}"
when: "item[1] in sources[src][item[0]].filenames"
The output of the map() filter is a number of lists the same length as "region". Jinja's "+" operator is the only mechanism I've found to join lists, but since it's a binary operator rather than a filter, I can't apply it to an arbitrary number of lists. The code above depends on "region" having length 2, and having to map() multiple times is ugly in the extreme.
Restructuring the data (or the problem) is not an option. The aspect I'd like to focus on is flattening the map() output, or some other way of generating the correct "msg:" lines the code above does
sum filter with start=[] is your friend:
region | map('extract',sources[src],'filenames') | sum(start=[])
From this:
[
[
"noreaster.txt",
"gusts.txt",
"drafty.txt"
],
[
"zephyr.txt",
"jetstream.txt"
]
]
It will do this:
[
"noreaster.txt",
"gusts.txt",
"drafty.txt",
"zephyr.txt",
"jetstream.txt"
]

How to convert a dictionary of dictionaries into a list of dictionaries in a Ansible vars file?

Within an Ansible vars file, I want to convert a dict of dicts into a list of dicts that I can pass to an external role from Ansible Galaxy.
Input:
postgres_users:
dc1:
name: user_dc1
password: pass_dc1
dc2:
name: user_dc2
password: pass_dc2
dc3:
name: user_dc3
password: pass_dc3
Desired output:
postgres_users:
- name: user_dc1
password: pass_dc1
- name: user_dc2
password: pass_dc2
- name: user_dc3
password: pass_dc3
Is there a simple way to do this within an Ansible vars file?
{{ postgres_users.values() | list }}
seems to do it.

Resources