0
votes

I have a list of an object type - TrackingMessage which contains a date. The list can contain 5 - several thousand entry's.

I need to go through that list comparing each date to find which one is the closest to the minute. The list could contain 3 or 4 entry's within a minute, so it needs to find the one closest to the minute before rounding it to the nearest minute.

i.e 12:45:22, 12:45:35, 12:45:15

From the small example above it would select 12:45:15 as that is closest to the minute, before rounding it to the nearest minute - 12:45:00.

The code I currently have is as follows:

Method which returns a list of the sorted and rounded messages based off the time

private List<TrackingMessage> findTrackingMessageDatesToNearestMinute(List<TrackingMessage> filteredTrackingMessages) {
    return filteredTrackingMessages.stream()
            .sorted(orderByClosestToMinute)
            .filter(JourneyServiceUtil.distinctByKey(x -> roundToClosestMinute(x)))
            .sorted(comparing(TrackingMessage::getEventTime))
            .collect(Collectors.toList());
  }

A comparator passed into the Stream

  public Comparator<TrackingMessage> orderByClosestToMinute = (tm1, tm2) -> {
    LocalDateTime roundedTrackingMessage1 = roundToClosestMinute(tm1);
    LocalDateTime roundedTrackingMessage2 = roundToClosestMinute(tm2);
    Duration tm1Duration = Duration.between(DateTimeUtils.toLocalDateTime(tm1.getEventTime()), roundedTrackingMessage1).abs();
    Duration tm2Duration = Duration.between(DateTimeUtils.toLocalDateTime(tm2.getEventTime()), roundedTrackingMessage2).abs();
    return tm1Duration.compareTo(tm2Duration);
  };

Function to round the time to nearest minute

  private LocalDateTime roundToClosestMinute(TrackingMessage trackingMessage) {
    return DateTimeUtils.toLocalDateTime(DateUtils.round(trackingMessage.getEventTime(), Calendar.MINUTE));
  }

Function to remove duplicates

  public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
  }

Now this code sort of works, but the response from the return of this code is as follows:

{
    "status": "success",
    "serviceId": "d8f2092b-01de-424f-9068-32c4efdcfd45",
    "driverId": "e1275ef6-f885-4724-9255-2e03868df5ee",
    "eventList": [
        {
            "eventTime": "2019-07-18T13:09:01",
            "eventId": 15,
            "lat": 50.4130166,
            "lon": -5.0739198
        },
        {
            "eventTime": "2019-07-18T13:10:04",
            "eventId": 21,
            "lat": 50.412991299999995,
            "lon": -5.073924
        },
        {
            "eventTime": "2019-07-18T13:11:04",
            "eventId": 21,
            "lat": 50.413074599999995,
            "lon": -5.074010299999999
        },
        {
            "eventTime": "2019-07-18T13:12:04",
            "eventId": 21,
            "lat": 50.4131295,
            "lon": -5.0739676
        },
        {
            "eventTime": "2019-07-18T13:13:04",
            "eventId": 21,
            "lat": 50.4133475,
            "lon": -5.073917499999999
        },
        {
            "eventTime": "2019-07-18T13:14:04",
            "eventId": 21,
            "lat": 50.4133618,
            "lon": -5.0739218
        },
        {
            "eventTime": "2019-07-18T13:15:04",
            "eventId": 21,
            "lat": 50.4133333,
            "lon": -5.073961
        },
        {
            "eventTime": "2019-07-18T13:16:04",
            "eventId": 21,
            "lat": 50.4132803,
            "lon": -5.0739765
        },
        {
            "eventTime": "2019-07-18T13:17:04",
            "eventId": 21,
            "lat": 50.4133433,
            "lon": -5.0739588
        },
        {
            "eventTime": "2019-07-18T13:18:04",
            "eventId": 21,
            "lat": 50.413306999999996,
            "lon": -5.0739355999999995
        },
        {
            "eventTime": "2019-07-18T13:19:04",
            "eventId": 21,
            "lat": 50.4133013,
            "lon": -5.073953599999999
        },
        {
            "eventTime": "2019-07-18T13:20:04",
            "eventId": 21,
            "lat": 50.413309299999995,
            "lon": -5.073988099999999
        },
        {
            "eventTime": "2019-07-18T13:21:04",
            "eventId": 21,
            "lat": 50.4133146,
            "lon": -5.0739618
        },
        {
            "eventTime": "2019-07-18T13:22:04",
            "eventId": 21,
            "lat": 50.413287999999994,
            "lon": -5.0739141
        },
        {
            "eventTime": "2019-07-18T13:23:04",
            "eventId": 21,
            "lat": 50.4132981,
            "lon": -5.0739008
        },
        {
            "eventTime": "2019-07-18T13:24:04",
            "eventId": 21,
            "lat": 50.413283,
            "lon": -5.07386
        },
        {
            "eventTime": "2019-07-18T13:25:03",
            "eventId": 52,
            "lat": 50.413303,
            "lon": -5.0738673
        },
        {
            "eventTime": "2019-07-18T13:26:01",
            "eventId": 60,
            "lat": 50.4135111,
            "lon": -5.0737745
        },
        {
            "eventTime": "2019-07-18T13:27:02",
            "eventId": 130,
            "lat": 50.415140099999995,
            "lon": -5.073355299999999
        },
        {
            "eventTime": "2019-07-18T13:28:00",
            "eventId": 125,
            "lat": 50.4139743,
            "lon": -5.0697981
        },
        {
            "eventTime": "2019-07-18T13:29:01",
            "eventId": 21,
            "lat": 50.4186421,
            "lon": -5.0698473
        },
        {
            "eventTime": "2019-07-18T13:30:09",
            "eventId": 153,
            "lat": 50.418105999999995,
            "lon": -5.0624391
        },
        {
            "eventTime": "2019-07-18T13:30:50",
            "eventId": 21,
            "lat": 50.4185011,
            "lon": -5.0581641
        },
        {
            "eventTime": "2019-07-18T13:31:59",
            "eventId": 21,
            "lat": 50.417915099999995,
            "lon": -5.0517363
        },
        {
            "eventTime": "2019-07-18T13:33:02",
            "eventId": 131,
            "lat": 50.4194111,
            "lon": -5.0496058
        },
        {
            "eventTime": "2019-07-18T13:35:34",
            "eventId": 51,
            "lat": 50.4194153,
            "lon": -5.049543
        }
    ]
}

As you can see (towards the bottom) we have two times within 13:30 {2019-07-18T13:30:50, 2019-07-18T13:30:09}

Technically I would want to be selecting the 2019-07-18T13:30:09 as that is closest to the minute (9 seconds away, where as the other is 10 seconds)

Also, if you look within the response the times have seconds and are not rounded down.

Please dont hesitate to ask any further questions if this has not been clear.

2
What are DateTimeUtils.toLocalDateTime and DateUtils.round?assylias
DateTimeUtils.toLocalDateTime converts a Date to LocalDateTime. DateUtils is apache library which rounds the time to nearest minute, hour or whatever you specifyAlexander Dunn

2 Answers

1
votes

What you seem to want is to calculate the seconds elapsed since the beginning of the minute. So in your example, 13:30:09 would be 9 seconds after the minute and 13:30:50 would be 50 seconds after the minute.

To calculate that you could use this method:

public static int distanceToMinute(LocalDateTime t) {
  LocalDateTime minute = t.truncatedTo(ChronoUnit.MINUTES);
  return (int) ChronoUnit.SECONDS.between(minute, t);
}

which will return 9 and 50 for the two examples you gave as expected.

The next step would be to group the times by their minutes, something like:

List<LocalDateTime> times = ...
Map<LocalDateTime, List<LocalDateTime>> timesByMinute = times.stream()
                    .collect(Collectors.groupingBy(t -> t.truncatedTo(ChronoUnit.MINUTES)));

And then reduce each list to the element that's closesest to the minute:

public static LocalDateTime bestTime(List<LocalDateTime> times) {
  return times.stream()
           .sorted((t1, t2) -> Integer.compare(distanceToMinute(t1), distanceToMinute(t2)))
           .findFirst().get();
}    
0
votes

I think one of problems could be this line:

.filter(JourneyServiceUtil.distinctByKey(x -> roundToClosestMinute(x)))

You're setting the key by rounding to the closest minute. The datetimes,2019-07-18T13:30:09 and 2019-07-18T13:30:50 will round to different minutes, 13:30:00 and 13:31:00. Therefore, they will have different keys and you'll get both of them in the results. You probably want to use floor here.

Also, you said the results are not rounded down. The filter sets the key to the rounded down object's eventTime value; however, it leaves the object untouched.