In a template sensor I have a look-up table implemented as a dictionary of values keyed by a number. By way of a MWE, let’s say the LUT is as follows:
{% set lut = {0: 'zero', 1: 'one', 2: 'two'} %}
I’d like to pull out the value with the key closest to a given value a which is determined elsewhere. So for example, when a = 1.2 I want to get 'one'. In Python I would use the following one-liner:
But this doesn’t work in a template: seems lambda functions aren’t available and min doesn’t take a key function anyway.
I’ve got this to work using a much more verbose loop-and-conditional construct:
{% set ns=namespace(diff=1e99, nearest=None) %}
{% for key, val in lut | items %}
{% set newdiff = (key - a) | abs %}
{% if newdiff < ns.diff %}
{% set ns.diff = newdiff %}
{% set ns.nearest = val %}
{% endif %}
{% endfor %}
{% set nearest = ns.nearest %}
Is there a more elegant way of accomplishing this, or is the loop the best I can do?
You’re over thinking this. If the “step size” is 1, just round the value.
{% set key = int(round(lut_value)) %}
{{ lut[key] }}
if the step size is not static, then you could make a macro and apply it to the keys. But thats going to use a list under the hood because sort is required. Meaning there’s no benefit. However this is how you’d do that.
{% set target_value = 3 %}
{% set lut = {0: 'zero', 1: 'one', 2: 'two'} %}
{% macro nearest(key, a, returns) %}
{{ returns(((key - a) | abs, key)) }}
{% endmacro %}
{% set nearest = as_function(nearest) %}
{% set key = lut.items() | map(attribute='0') | map('apply', nearest, target_value) | sort(attribute='0') | first | last %}
{{ lut[key] }}
Thanks @petro. Although the minimal working example has simple unit increments in the keys, the general case in my application doesn’t necessarily: the actual keys are subject to change periodically so I can’t guarantee there will be integer steps. Though it is a nice simplification if the LUT does contain integer keys with unit step size.
I did start exploring the macro route too, but found that it didn’t seem any less complex than the for loop (at least for somebody who doesn’t use functional programming regularly). Thanks for clarifying that indeed it doesn’t provide a significant benefit over a simple for loop. Though I have just learned about attribute='n' to get the n-th element of a list/tuple, which is cool. I thought that would only work with named attributes.