Help with test of a list of numbers

I’ve been beating this around for half a day, and decided to get some expert help.

I want to test a list to make sure it’s a valid RGB color list.

So first I need to make sure it’s a list, and that the list has 3 members. Then I need to test that each member is a number, an integer, and between 0 & 255.
If all that is true return true. If any is not true, return false.

I have pieces parts working, but am ending up with multiple true false things.

{%- set _rgbl = [125,11.3,183] %}

{%- if _rgbl is list and _rgbl | count == 3  %}
{%- for clr in _rgbl %}
{%- if is_number(clr) and int(clr,0) == clr and 0 <= clr <= 255%}
True
{%- else %}
False
{%- break %}
{%- endif %}
{%- endfor %}

True
{%- else %}
False
{%- endif %}

That example is set to fail on the second number.
So the question is really how to get the first test result at the list level melded with the test of the list member test results so that the return is true or false.

In developer this above results in

True
False

True

The first two (True/False) are due to the loop. First number fits criteria. Second number is not an int.

The third (True) is because there are three in the list.

I think the best approach is set a field to true in the beginning. then, if you hit any false condition, change it to false.

{%- set _rgbl = [125,11.3,183] %}
{%- set ns = namespace(good_color=true) -%}
{%- if _rgbl is list and _rgbl | count == 3  %}
  {%- for clr in _rgbl %}
    {%- if not(is_number(clr) and int(clr,0) == clr and 0 <= clr <= 255)%}
      {%- set ns.good_color = false -%}
      {%- break %}
    {%- endif %}
  {%- endfor %}
{%- else %}
  {%- set ns.good_color = false -%}
{%- endif %}
{{ ns.good_color }}

What about moving the if inline with the for, then using loop variables:

{%- if _rgbl is list and _rgbl | count == 3  %}
{%- for clr in _rgbl if is_number(clr) and int(clr,0) == clr and 0 <= clr <= 255 %}
{% if loop.last %}
{{ loop.index == 3 }}
{%- endif %}{% endfor%}{%- endif %}
1 Like

I like that!

But, you need to remove the count check in the first if. Otherwise, when not three, you just drop out with nothing.

Oh, don’t remove it. Just put a false before the last endif. Because, if not a list, you just drop out too.

{%- if _rgbl is list and _rgbl | count == 3  %}
{%- for clr in _rgbl if is_number(clr) and int(clr,0) == clr and 0 <= clr <= 255 %}
{% if loop.last %}
{{ loop.index == 3 }}2
{%- endif %}{% endfor%}{% else %}False{%- endif %}
1 Like

Why doesn’t comparing lists directly work for negative numbers?

{{ [255,-30,255] <= [255,255,255] }} -> True, good

{{ [255,500,255] <= [255,255,255] }} -> False, good

{{ [0,0,0] <= [255,-30,255] }}  -> True,  why?
{{ _rgbl is list and
   _rgbl | select('integer') | list | count == 3 and
   _rgbl | select('<', 0) | list | count == 0 and
   _rgbl | select('>', 255) | list | count == 0 }}
Confirm it's a list and
All three items are integers and
None are less than 0 and
None are greater than 255
4 Likes

Both solutions work. I think I’m going with the first one because that’s where my head was trying to get to, but @123 you solution is certainly cleaner.

I’m working on my Color Multi tool macro, and will put your names @Didgeridrew @jeffcrum in the credits as contributors. Thanks for the help!

So this is weird. Only one element comparison is required to fail the test if all numbers are positive yet two elements are required to fail the test for negative numbers:

{{ [0,0,0] < [255,-30,255] }} -> True
{{ [0,0,0] < [-30,-30,255] }}  -> False

Bug?

Or am I not understanding something?

It looks like it’s only testing the first one maybe.

I love the thinking. But, I think it is comparing the lists as strings there.

{{ [0,0,0] < [255,-30,255] }} -> True
{{ [0,0,0] < [-30,-30,255] }} -> False
{{ '0,0,0' < '255,-30,255' }} -> True
{{ '0,0,0' < '-30,-30,255' }} -> False
{{ '0' < '-255' }} -> False
1 Like

Personally, I’d use @123 answer. That is sooooo damn clean!!!

I have to write one for xy and hs color as well, I’ll use his for xy and credit @123 . :slight_smile:
That way I’ll be able to find both ways to do it when I need to again.

If you prefer a for-loop, just throw all the tests into its if section and count how many list items pass all of them. If the count is 3 then it’s true otherwise false.

{%- set ns = namespace(x = 0) -%}
{%- for clr in _rgbl if _rgbl is list and _rgbl | count == 3 and
   clr is integer and 0 <= clr <= 255 %}
  {%- set ns.x = ns.x + 1 -%}
{%- endfor %}
{{ ns.x == 3 }}

Now, you are just showing off! :rofl: :rofl:

Just tried something freaky to break it…
[200,--125,0] also passes.
So does any even number of “-” signs…

You can use this to remove any even number of negative signs (or reduce an odd number of negative signs to just one).

{% set _rgbl = tuple(_rgbl) | list %}

However I believe the double-negative value passed the original battery of tests because a double-negative is understood to be a positive value … but I don’t know if Home Assistant will accept it as a valid value for setting a light’s RGB value. Worth testing to see if the conversion I suggested above is even needed.

I’m going to ignore the double negative… If they have that in there they deserve to get an error.

For completeness, here are the xy and hs list checks I’m using.

{{- _xyl is list and
   _xyl | select('number') | list | count == 2 and
   _xyl | select('<', 0) | list | count == 0 and
   _xyl | select('>', 1) | list | count == 0 }}


{{- _hsl is list and
   _hsl | select('number') | list | count == 2 and
   _hsl | select('<', 0) | list | count == 0 and
   _hsl[0] <= 100 and
   _hsl[1] <= 360 }}