-3

I have an object that follows the structure below

class Ray{
   float param1; 
   float param2; 
}

and then I have the following: List<Ray> rays

I have a 2D slider that gives a couple of values {value1, value2}

The problem is that the values inside the slider are continuous and they don't match exactly with the values of the class Ray, thus I cannot just make a filter using stream().

I need to find the Ray that has both fields closest to value1 and value2.

I'm not really expert using streams().

My first idea is to iterate the List and find the nearest value (let's say nearestParam1) to value1, then iterate the list and find the nearest value (nearestParam2) to value2, then filter the list by nearestParam1 and finally filter the list by nearestParam2.

... 
List<Ray> rays;
float nearestParam1;
float value1;
float oldDistance=9999;
for (Ray ray : rays){
 float newDistance = abs( ray.Param1 - value1);
 if(newDistance < oldDistance) {
  oldDistance = newDistance;
  nearestParam1 = ray.Param1;
 }
}

List<Ray> filteredRays = rays.stream().filter(ray -> ray.Param1 == nearestParam1).toList();

and then repeat again for the second field. I don't like this solution, can you find a cleaner and maybe faster solution?

My list has a really huge dimension and inside there are not equals object (1 field could match but not both at the same time)

WhiteSte
  • 1
  • 4
  • Sound like XY-problem. Why you're using these `int` and `float` variables to represent date-time information? You can use classes from `java.time` package, no need to reinvent them. See https://docs.oracle.com/javase/tutorial/datetime/overview/index.html – Alexander Ivanchenko Aug 03 '22 at 11:39
  • I need my custom parameters for internal computations – WhiteSte Aug 03 '22 at 12:18
  • It doesn't explain why can't you use [`LocalDateTime`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/LocalDateTime.html) instead of your custom object. You can extract hours, minutes, day the year, etc. from it as `int`. – Alexander Ivanchenko Aug 03 '22 at 12:23
  • I posted a particular case but these parameters have not to be always date time. Sometime they could be intended as theta and phi (azimuth and elevation angles) – WhiteSte Aug 03 '22 at 12:38
  • So you need a uniform logic for an object containing two numeric fields. And the goal is to find an object with the first field that matches exactly and the second having the closest value? Are you sure you want it to be coupled to work only with the `Ray` class? – Alexander Ivanchenko Aug 03 '22 at 12:46
  • Yes exactly! I have different kind of rays that may store different physics properties. In general these two parameters (like for example day of the year and time of the day) identify uniquely the orientation and the elevation of the ray (spheric coordinates) . With a continuous 2D slider I need to catch the exact ray given these two parameters. I'm sorry if I explained badly in the first time – WhiteSte Aug 03 '22 at 12:51
  • Also you have to take into account that you might lose precision with floating point numbers (`float` and `double`) https://stackoverflow.com/questions/322749/retain-precision-with-double-in-java . Hence, in some cases `ray.Param1 == nearestParam1` might not work as expected. You can use `BigDecimal` or instead of precise compassion allow the values of `nearestParam1` to differ by let's say `0.0001` and they will still consider to be equal. – Alexander Ivanchenko Aug 03 '22 at 12:54

2 Answers2

0

Rather than comparing each field separately, you can consolidate them into a single value and then find the closest one to your target date using Stream#min.

public LocalDateTime toDateTime(int dayOfTheYear, double timeOfTheDay) {
    LocalDate first = LocalDate.of(2022,1,1);
    return first.atStartOfDay()
            .plusDays(dayOfTheYear)
            .plusHours((int) timeOfTheDay)
            .plusMinutes((long) ((timeOfTheDay - Math.floor(timeOfTheDay)) * 60L));
}

public Optional<Ray> findClosestRay(List<Ray> rays, int DayOfTheYear, float TimeOfTheDay) {
    LocalDateTime target = toDateTime(DayOfTheYear, TimeOfTheDay);

    return rays.stream()
            .min(Comparator.comparing(ray -> Duration.between(target, toDateTime(ray.DayOfTheYear, ray.TimeOfTheDay)).abs()));
}
MikeFHay
  • 8,562
  • 4
  • 31
  • 52
  • I specified badly my question, I've just edited. The date parameters were an example. I can get parameters that cannot be merged into a single value – WhiteSte Aug 03 '22 at 12:45
  • Your question is still under-specified. How do you expect to consolidate the results of multiple fields? If `ray1` is closest per `param1`, but `ray2` is closest per `param2`, then how will you determine which is single closest result over all? – MikeFHay Aug 03 '22 at 14:05
  • That's an interesting point, I suppose a good way is to sum the difference between params and inputs. I found a way, I'm editing again the main post – WhiteSte Aug 03 '22 at 15:14
  • A bit rude to use what is essentially my answer and not even upvote me ‍♂️ – MikeFHay Aug 04 '22 at 09:32
  • I upvoted you but i don't have enough rep. Anyway in my answer i referenced to you – WhiteSte Aug 04 '22 at 10:19
  • Ah fair enough :) – MikeFHay Aug 04 '22 at 10:20
0

as MikeFHay pointed out in the main post i didn't specified how to determine the single closest result over all rays combining minimization of param 1 and param 2.

I found a solution that is quite working for my case, what do you think about that?

Comparator<Ray> comparator = Comparator.comparing(ray->abs(ray.param1-value1) + abs(ray.param2-value2) );

Ray bestRay = rays.stream().min(comparator).get();
WhiteSte
  • 1
  • 4