[SOLVED] findFirst[ ] from Group doesn't find a match

My point with that was that MIN is a calculated value, like say AVG. I’ve no idea of the nuts and bolts but can easily imagine it producing 2 from an Item 2.0 or vice versa, and that sometimes causes problems e.g some persistence will save 2 and restore 2.0, they are not identical.
It’s just a suggestion as to why you might not get a match.
If you intend to use this group search technique it would pay to verify how that pans out.

Okay. You said that other was the only line in the rule, hence the question. So your rule then-block is now

var cheapestItem
cheapestItem = gSpritpreise.members.findFirst[ p | p.state == gSpritpreise.state ]

And the jvmvoid error persists, right? I cannot see the problem with that, it might return null but nothing would try to use that.

I shall do some testing then. However, at least when I output the values from the individual Items and the Group-Items to the log they do match.

I don’t use

var cheapestItem

When I do, the original error disappears, but I can’t use the Item with .state, .name or similar. The error

Error during the execution of rule 'Test': 'state' is not a member of 'cheapestItem'

appears. When I then just use cheapestItem without anything the log says:

Error during the execution of rule 'Test': An error occurred during the script execution: Could not invoke method: org.eclipse.xtext.xbase.lib.ObjectExtensions.operator_plus(java.lang.Object,java.lang.String) on instance: null

This probably means that indeed it can’t find a match, thus the Item is null, but it doesn’t solve my problem. I’ll change the name of the topic. :wink:

Well that was all a wasted effort.

Going back to your actual requirement, you just want to find the minimum value in a group of number Items? (i.e. you don’t really care about matching MIN)
Do you want to include or exclude NULL or UNDEF states?

I shall summon @rikoshak who knows a few nifty group filter tricks

Well, the thread title was maybe ill-posed by including the error message. But the goal was stated quite clearly.

Beyond that I only described my observations. I do have a work-around solution by now through looping over all group members and writing Item states to local variables. That’s however far messier code and as far as I understand it should work using findFirst as well.

Thanks for your help so far, @rossko57.

This is correct. Each step of the recalculation results in an update so there will be N-1 updates where N is the number of members in the Group.

It will match, but only if 2.0 is actually 2.0. The way that floating point numbers are represented, not every possible floating point value can be represented. Consequently when you have a value that can’t be represented (e.g. 9.2) you end up with a value that is really close to 9.2. But all those little errors can add up so it’s really easy if you’ve done a calculation where you have the value 2.0000003 instead of 2.0 and if you use == to the integer 2 it will return false. This is why, as a general rule, == and != should never be used with floating point numbers except in cases where the floating point number is rounded first.

In this case though, dancemichi is right. The state of the Group that get’s calculated should exactly match the state of one of the Items because there isn’t any math taking place. It just postUpdate’s the Group’s state with the state from the min Item. I don’t think a conversion or the like is at play here.

Almost, but I’m a tricky deamon. You have to get my name exactly right. :japanese_ogre: :smiley:


The error doesn’t make a whole lot of sense to me. If findFirst doesn’t find a match, it returns null, not void. How did you declare cheapestItem? Did you give it’s type? The type of the variable get’s defined either explicitly, or by inferring the type based on the value that get’s passed to it when it get’s declared.

If you declare:

var cheapestItem

I think cheapestItem’s type will be void since you didn’t give the language any other idea about what type it should be.

var cheapestItem = null

I’m not sure what type cheapestItem will be. I don’t think it will be void but it doesn’t have anything else to infer a type. Maybe it would be Object.

In order to set the type correctly you need to supply it. Either

var NumberItem cheapestItem

or

var cheapeastItem = gSpritpreise.members.findFirst[ p | p.state == gSpritpreise.state ] as NumberItem

Both of these lines tell the Rules engine that cheapestItem needs to be a NumberItem.

on instance: null

This error is somewhat misleading. When the Rules Engine in unable to convert the type of a variable to one appropriate for the operation you are trying to do, a null exception is generated. Thus, it doesn’t necessarily mean that the findFirst failed to return a value . It could be the value returned cannot be used with the + operator.

I would expect

val cheapestItem = gSpritpreise.members.findFirst[ p | p.state == gSpritpreise.state ] as NumberItem

to work.

2 Likes

Now that’s literally unbelievable. It does work! Well, I initially forgot to use ‘val’ or ‘var’ at the beginning of that line (that’s when the above described ‘assignValueTo’ error occurred), but I could make an oath I tested that line before after comparing to my own usage of findFirst in other rules, where it does work as expected. I began to suspect that it indeed has to do with the matching condition comparing numbers, which I weren’t in need of before.

I’m new to this forum as an active user, but have followed many threads before. So maybe it’s just some of @rlkoshak’s magic going on here. :wink:

Thanks a lot!

Don’t forget the race still exists, leaving you with a potential no-match.
The longhand iteration and compare to find a current minimum will at least find something, with the possibility it’s a few milliseconds out of date.

(Now I’m academically curious as to which of a member at 2 and a member at 2.0 will be seen as MIN :wink:)

1 Like

Maybe we are lucky and @rlkoshak even knows whether a group’s state is calculated upon an update of one or more of its members before any rules are triggered, or not.

Well, what’s your rule triggered upon? Group state change - see earlier about how the group state changes. Indeterminate how far you might get through any triggered rules while the group calculation is in progress. Can’t wish this race away.

You can avoid it by introducing a delay between trigger and your search.

Currently the rule is triggered upon a change of state in any member of the group. Using the group itself as a trigger would run the rule multiple times, since…

Didn’t think of that. That should circumvent the potential problem. Nice idea. Guess I could start a 1 sec. Timer or something inside the rule.

Ohh less than that - try just 20mS sleep (probably 5 times what it needs to be)

Of course a second change might still happen along :crazy_face:
I’d still code for a no-match,no action failsafe if possible.

It’s all a big jumble which is rossko57’s point I think.

At each step through the comparisons of the members with the Group the Group’s state gets updated. So the Rule could be triggered before the actual min is arrived at.

I added a 1-sec Thread::sleep() by now. Hope that circumvents said issues. Following your suggestion I do an additional NULL and UNDEF check now. Seems to work fine so far.

1 Like

You could go a much shorter delay. When triggering from member of, you know that a group recalculation/update is in progress or imminent. But it will only take a few milliseconds.
Waiting longer doesn’t reduce the chances of nothing else happening - it’s just tying up resource.

I know and I quite understand the urge for optimization, but frankly I don’t care about that second :wink: . The rule won’t be triggered that often, anyway.

Is not about optimization. Long running rules can be dangerous. Sleeps longer than 300 msec are risky. You can run the risk of delays in all your rules or all your rules just stopping running in extreme circumstances. See (OH 1.x and OH 2.x Rules DSL only] Why have my Rules stopped running? Why Thread::sleep is a bad idea.

Thanks for the heads-up. Since I’m triggering my rule using ‘Member of gSpritpreise changed’ and all five members of the group might get updated (more or less) simultaneously no further rules can run for at least a full second with my Thread::sleep(1000), if I understood the following correct?

I just changed to a createTimer solution, as suggested in your linked post. Which I use anyway in all other rules.

Correct.

1 Like