How to evaluate difference between 2 lists

HI,

Using CC Variable which creates a history of states, in this case populated with playing media_players, I’d like to compare 2 history states, and output the difference.

If the difference is an extra player playing, Id like to turn_on that media_player. If the difference is 1 less, Id like to turn_off that media_player.

ex:

as you can see I turned off player dorm_marte.

So Id like the diff between the 2 to be media_player.google_home_dorm_marte, and since it is one less, it needs to be turned_off.

Would this be possible in jinja templates, and if not maybe python, but then again: how?..

rationale:
I now have to stop playing all then turn on all checked players, when (un)checking players. Which is silly, because the players that are not touched, should nt have to be stopped and turned on again.

Please have a look, thanks!

{{ states.variable.last_active_radios.attributes.history_1 | reject('in', states.variable.last_active_radios.attributes.history_2)  | list }}
1 Like

HI!

Cool, had hoped something like this would exist.

Needs some finetuning though :wink:

first iteration (at startup, None is value)

11

second go:

teken one out again:

22

what do they look like when you print them out? Are they strings or lists? Is this one of your python scripts that populates this?

If it’s a python script, then we should change that attribute into a list. As it should be a list in the first place.

If it’s not controlled by you or it’s populated by an automation of some sort, i.e not python, then you’ll have to do some type checking to verify that it’s a string or list before using that reject method.

We know that no matter the response:

  1. It’s never exactly what you want.
  2. You already knew it.
  3. You change the requirements.
  4. It won’t get a :blue_heart:

The toughest customer in the community … :wink:

3 Likes

not really positive about that, have a look:

I can enter this as an entity_id to the media_player.media_play command, so would have thought it to be a comma separated list.

no this is from the CC Variable by @rogro82

Elaborate?

So it is a string that looks like a list, is the separator just a comma or is it a comma and a space?

sure. Github: GitHub - rogro82/hass-variables: Home Assistant variables component

this is the variable declaration:

##############################################################################################################
# Variable
##############################################################################################################
variable:
  last_active_radios:
    value: 'Not set'
    restore: true
    attributes:
      icon: mdi:radio

and this the automation that sets the values and attributes:

automation:

# Update Last active radio
  - alias: 'Update Last active radios'
    id: 'Update Last active radios'
    initial_state: 'on'
    trigger:
      platform: state
      entity_id: sensor.media_players_active
    action:
      service: variable.set_variable
      data:
        variable: last_active_radios
        attributes_template: >
          {
                "history_1": "{{ variable.state }}",
                "history_2": "{{ variable.attributes.history_1 }}"
          }
      data_template:
        value: >
          {{ trigger.to_state.state }}

it creates the same output format as:

        value_template: >
          {% set players_playing = states.media_player|selectattr('entity_id','in',state_attr('group.radio_players','entity_id'))
            |selectattr('state','eq','playing') |map(attribute='entity_id')|join(', ') %}
          {% if players_playing | length == 0 %} None
          {% else %} {{players_playing}}
          {% endif %}

Ok, so this will give you unique names in both lists and filter out the similarities.

{% set a = state_attr('variable.last_active_radios','history_1') %}
{% set b = state_attr('variable.last_active_radios','history_2') %}
{% if a and b %}
  {% set a = a.split(', ') if a and a.count(', ') >= 1 else [ a ] %}
  {% set b = b.split(', ') if b and b.count(', ') >= 1 else [ b ] %}
  {{ (a | reject('in', b) | list + b | reject('in', a) | list) | join(', ') }}
{% else %}
  No differences
{% endif %}

yess! magic, this is really getting very close, thank you so much.

look whats happening:

I changed your template to:

{% set a = states('variable.last_active_radios') %}
{% set b = state_attr('variable.last_active_radios','history_1') %}
{% if a and b %}
  {% set a = a.split(', ') if a and a.count(', ') >= 1 else [ a ] %}
  {% set b = b.split(', ') if b and b.count(', ') >= 1 else [ b ] %}
  {{ (a | reject('in', b) | list + b | reject('in', a) | list) | join(', ') }}
{% else %}
  No differences
{% endif %}

to calculate the last made difference. And it is spot perfect!

Since I need the output to be entered to either the media_player.play_media command or media_player.turn_off the ‘None’ should be filtered out. would that be possible?

Also, how can I see if the filtered player is an added player, or a stopped player? As to select the correct command :wink:

About the else, I know there should be a safeguard at all times. This however is intended by you as merely pro forma? Since the variable will only be set upon change/difference. So this state would never be met?

really sorry if I disappointed you somewhere along the many conversations. Ive even hardcoded you in several of my packages :heart:

serious: really appreciate all the help you guys throw at this community, and try to return the favor as much as I can. Please don’t stop…

Maybe instead of ‘No differences’ it would return an empty list?

{% else %}
  []
{% endif %}
{% set a = states('variable.last_active_radios') %}
{% set b = state_attr('variable.last_active_radios','history_1') %}
{% if a and b %}
  {% set a = a.split(', ') if a and a.count(', ') >= 1 else [ a ] %}
  {% set b = b.split(', ') if b and b.count(', ') >= 1 else [ b ] %}
  {{ (a | reject('in', b) | list + b | reject('in', a) | list) | reject('eq', 'None') | list | join(', ') }}
{% else %}
  No differences
{% endif %}

No clue what added player means. As for stopped…

{% set a = states('variable.last_active_radios') %}
{% set b = state_attr('variable.last_active_radios','history_1') %}
{% if a and b %}
  {% set a = a.split(', ') if a and a.count(', ') >= 1 else [ a ] %}
  {% set b = b.split(', ') if b and b.count(', ') >= 1 else [ b ] %}
  {% set entity_ids = (a | reject('in', b) | list + b | reject('in', a) | list) | reject('eq', 'None') | list }}
  {% set stopped = states.media_player | selectattr('entity_id','in', entity_ids) | selectattr('state','eq','stopped') | map(attribute='entity_id') | list | join(', ') %}
  {{ stopped }}
{% else %}
  No differences
{% endif %}

No you need to have a result, it can be an empty string or something to key off of.

What happens when they have equal lists? This will change.

sorry, my fault not explaining well enough how this is setup.

for each player I have an input_boolean. turning it on ‘adds’ the player to the list of players, turning it off, takes it out of the list. either move is a change, recorded in the variable.

so the list gets longer or shorter. if it gets shorter, a player is taken out, I want to stop that player. and vice versa for adding player and playing it.

reject(‘eq’, ‘None’)

bingo! works the works.

Does anyone know why these two lists I have won’t work in this formula?

Here are the lists:

The two lists are currently the same, but occasionally one or two of the five-digit numbers is repeated somewhere in the first array, and I’d like a template to pick those out.

I’d send screenshots of me plugging these into the formula provided in this thread, but when I do that the result just comes back blank. I assume I’m not splitting correctly, or that the square brackets are the issue. I’ve tried splitting on different characters and regex_replacing the brackets, but I can’t get any result.

Six years have passed since this topic was created. Home Assistant now supports sets which have native methods for determining difference, intersection, etc.

You currently have two lists. You can easily convert the lists to sets and then use the difference method to report what is in one set but not the other.

To understand what I have suggested, copy-paste the following template into the Template Editor and observe the results.

{% set a = set([10, 20, 30, 40, 50, 60]) %}
{% set b = set([20, 40, 60, 80]) %}
{{ a.difference(b) | list }}
{{ b.difference(a) | list }}

Adapt it to meet your requirements.