Determine relationship between objects with java 8 streams api

M. Schröder Source

recently I solved the following problem:

Given a chronologically ordered list of LocalDateTime, find the average duration between neighbours.

I did the following:

@Test
public void canCalculateAverageDuration() {
    final LocalDateTime now = LocalDateTime.now();
    final List<LocalDateTime> localDateTimes = Arrays.asList(now, now.minusHours(5), now.plusMinutes(2));

    final List<Duration> durations = new ArrayList<>();
    localDateTimes.stream()
        .sorted()
        .reduce((first, second) -> {
            durations.add(Duration.between(first, second));
            return second;
        });

    final OptionalDouble averageNanos = durations.stream()
            .mapToDouble(Duration::toNanos)
            .average();

    final Duration average = Duration.ofNanos((long) averageNanos.orElse(0.0));
    assertThat(average).isEqualTo(Duration.parse("PT2H31M"));
}

I wonder if the problem could be solved in a more elegant way, for example: I would like to avoid the List of durations if possible. What do you think?

javajava-8java-streamreduce

Answers

answered 6 months ago M. Prokhorov #1

You could solve this just using iterations (i.e. not using Streams):

@Test
public void canCalculateAverageDuration() {
  final LocalDateTime now = LocalDateTime.now();
  final List<LocalDateTime> localDateTimes = Arrays.asList(
      now,
      now.minusHours(5),
      now.plusMinutes(2)
  );
  localDateTimes.sort(Comparator.naturalOrder());

  LocalDateTime previous = null;
  LongSummaryStatistics stats = new LongSummaryStatistics();
  for (LocalDateTime dateTime : localDateTimes) {
    if (previous == null) {
      previous = dateTime;
    }
    else {
      stats.accept(Duration.between(previous, dateTime).toNanos());
    }
  }

  final Duration average = Duration.ofNanos((long) Math.ceil(stats.getAverage()));

  assertThat(average).isEqualTo(Duration.parse("PT2H31M"));
}

Whether or not this is more elegant is subject to personal preference, but this version uses no intermediate collections at least.

answered 6 months ago M. Schröder #2

I just found this:

Collections.sort(localDateTimes);
final double average = IntStream.range(0, localDateTimes.size() - 1)
    .mapToLong(l ->
        Duration.between(
            localDateTimes.get(l),
            localDateTimes.get(l+1))
        .toNanos())
    .average().orElse(0.0);
assertThat(Duration.ofNanos((long) average)).isEqualTo(Duration.parse("PT2H31M"));

comments powered by Disqus