개발공부/JAVA

연속적이지 않은 시간 확인

민둥곰 2023. 12. 19. 17:09

요구사항

  1. 특정 시간 Column에서 누락이 발생할 경우 해당 시간대에 얼마나 누락이 발생되었는지 확인 필요
  2. 시작 시간과 끝 시간이 누락될 경우 해당 시간에서 얼마나 누락이 발생하였는지 확인 필요
  3. 전체가 누락될 경우 누락된 전체 시간 확인 필요
  4. 해당 데이터는 List 형태로 제공

기능 정리

  1. 연속적이지 않은 시간대와 해당 시간에 얼마나 누락이 발생하였는지 확인 필요
  2. 연속적인 기준은 초/분/시간이며 해당 로직 구성 필요
  3. 시작 시간이 List 첫번째 인덱스에 없을 경우, 마지막 시간이 List 마지막 인덱스에 없을 경우 추가
    1. 해당 시간에 얼마나 누락이 발생하였는지 확인 필요

기능 구현

선택한 방법은 전체를 순환하며 n 값과 n+1의 값을 비교 (n == (n+1) + 특정 시간(1초, 1분 ...))

시간 복잡도: $$O(nlog_n)$$

전체 코드

Enum (TimeUnit.java)

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.function.BiFunction;

public enum TimeUnit {
    SECOND(LocalDateTime::plusSeconds, (duration, ignored) -> duration.getSeconds()),
    MINUTE(LocalDateTime::plusMinutes, (duration, ignored) -> duration.toMinutes()),
    HOUR(LocalDateTime::plusHours, (duration, ignored) -> duration.toHours()),
    DAY(LocalDateTime::plusDays, (duration, ignored) -> duration.toDays());

    private final BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction;
    private final BiFunction<Duration, Long, Long> durationFunction;

    TimeUnit(
            BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction,
            BiFunction<Duration, Long, Long> durationFunction) {
        this.plusFunction = plusFunction;
        this.durationFunction = durationFunction;
    }

    public LocalDateTime plusFunction(LocalDateTime time, Long seconds) {
        return plusFunction.apply(time, seconds);
    }

    public Long durationFunction(Duration duration, Long seconds) {
        return durationFunction.apply(duration, seconds);
    }
}

Class (TimeFinder.java)

import lombok.extern.slf4j.Slf4j;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Slf4j
public class TimeFinder {
    private final List<LocalDateTime> timeStamps;
    private final Long timeDuration;
    private final TimeUnit timeUnit;

    private static final Long SECOND = 1L;
    private static final Long MINUTE = SECOND * 60L;
    private static final Long HOUR = MINUTE * 60L;
    private static final Long DAY = HOUR * 24L;

    private TimeFinder(List<LocalDateTime> timeStamps, Long timeBranch) {
        validate(timeStamps, timeBranch);
        this.timeStamps = new LinkedList<>(timeStamps);
        this.timeUnit = determineTimeUnit(timeBranch);
        this.timeDuration = determineDurationTime(timeBranch);
    }

    private TimeFinder(List<LocalDateTime> timeStamps, Long timeBranch, LocalDateTime startTime, LocalDateTime endTime) {
        this.timeUnit = determineTimeUnit(timeBranch);
        this.timeDuration = determineDurationTime(timeBranch);
        this.timeStamps = checkRangeTimeReturnTimeStamps(timeStamps, startTime, endTime);
        validate(this.timeStamps, timeBranch);
    }

    /**
     * 전체가 누락될 경우 전체 누락 시 가장 처음 시간과 마지막 시간을 처리하기 위한 로직 추가
     * 반환 값은 전체 시간 List<LocalDateTime>
     *
     * @param timeStamps 누락을 확인할 원본 시간
     * @param startTime 누락을 확인할 첫 시간
     * @param endTime 누락을 확인할 마지막 시간
     * @return List<LocalDateTime>
     */
    private List<LocalDateTime> checkRangeTimeReturnTimeStamps(
            List<LocalDateTime> timeStamps,
            LocalDateTime startTime,
            LocalDateTime endTime) {

        LinkedList<LocalDateTime> result = new LinkedList<>(timeStamps);

        LocalDateTime firstTime = this.timeUnit.plusFunction(startTime, (this.timeDuration) * -1);
        LocalDateTime lastTime = this.timeUnit.plusFunction(endTime, this.timeDuration);

        if(!(result.getFirst().equals(startTime))) {
            result.addFirst(firstTime);
        }

        if(!(result.getLast().equals(endTime))) {
            result.addLast(lastTime);
        }

        // 전체 누락일 경우 timeStamps 초기화
        if(result.isEmpty()) {
            log.warn("[Stats omission] Omit all row time ");
            timeStamps = new ArrayList<>();
            timeStamps.add(0, firstTime);
            timeStamps.add(1, lastTime);
        }

        return new ArrayList<>(result);
    }

    public static Map<LocalDateTime, Long> findHole(
            List<LocalDateTime> timeStamps,
            Long timeBranch,
            LocalDateTime start,
            LocalDateTime end) {

        Map<LocalDateTime, Long> result = new LinkedHashMap<>();
        try {
            TimeFinder timeFinder = new TimeFinder(timeStamps, timeBranch, start, end);

            result = timeFinder.findNonConsecutiveTimes();

        } catch (IllegalArgumentException e) {
            log.error(e.getLocalizedMessage());
        }

        return result;
    }


    /**
     * Map<LocalDateTime, Long> 반환
     * 시간/분/초 단위로 누락된 시간의 첫 시간과 해당 Count 확인
     *
     * @return Map<LocalDateTime, Long> : 처음 누락된 시간(LocalDateTime), 시간 단위 별 Count(Long)
     */
    private Map<LocalDateTime, Long> findNonConsecutiveTimes() {
        int maxTraversal = timeStamps.size() - 1;

        // 누락 부분 확인(filter())
        // 첫 누락 시간(plusFunction())
        // 누락 Count(durationFunction())
        Map<LocalDateTime, Long> result = IntStream.range(0, maxTraversal)
                .filter(i ->
                        !timeUnit.plusFunction(timeStamps.get(i), timeDuration)
                                .equals(timeStamps.get(i + 1))
                )
                .boxed()
                .collect(Collectors.toMap(
                        i ->
                                timeUnit.plusFunction(timeStamps.get(i), timeDuration),
                        i ->
                                timeUnit.durationFunction(Duration.between(timeStamps.get(i), timeStamps.get(i+1)), timeDuration) - 1
                ));

        // Time 정렬 후 반환
        // LinkedHashMap : 삽입이 많이 발생하여 선택
        return result.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (time, count) -> time, LinkedHashMap::new
                ));
    }

    private static void validate(List<LocalDateTime> timeStamps, Long timeBranch) {
        validateTimeBranchIsPositive(timeBranch);
        validateTimeBranchIsPositiveTime(timeBranch);
        validateTimeStampsIsNull(timeStamps);
    }

    private static void validateTimeStampsIsNull(List<LocalDateTime> timeStamps) {
        if (timeStamps == null || timeStamps.isEmpty()) {
            throw new NullPointerException("[Somission] Timestamps is null");
        }
    }

    private static void validateTimeBranchIsPositive(Long timeBranch) {
        if (timeBranch <= 0) {
            throw new IllegalArgumentException("[omission] Invalid timeBranch value (Stat)");
        }
    }

    private static void validateTimeBranchIsPositiveTime(Long timeBranch) {
        if (isMinute(timeBranch)) {
            if (Math.floorMod(timeBranch, MINUTE) != 0) {
                throw new IllegalArgumentException("[omission] Time branch is negative (Minute)");
            }
        }

        if (isHour(timeBranch)) {
            if (Math.floorMod(timeBranch, HOUR) != 0) {
                throw new IllegalArgumentException("[omission] Time branch is negative (Hour)");
            }
        }

        if (isDay(timeBranch)) {
            if (Math.floorMod(timeBranch, DAY) != 0) {
                throw new IllegalArgumentException("[omission] Time branch is negative (Day)");
            }
        }
    }

    /**
     * timeBranch 따라 Plus, Duration between 함수 결정
     *
     * @param timeBranch 비교할 시간의 기준(단위: 초)
     * @return TimeUnit 시간의 기준에 따라 TimeUnit 반환
     */
    private static TimeUnit determineTimeUnit(Long timeBranch) {
        if (isMinute(timeBranch)) {
            return TimeUnit.MINUTE;
        }

        if (isHour(timeBranch)) {
            return TimeUnit.HOUR;
        }

        if (isDay(timeBranch)) {
            return TimeUnit.DAY;
        }

        return TimeUnit.SECOND;
    }

    /**
     * timeBranch 크기에 따라 초/분/시의 기준 값 결정
     *
     * @param timeBranch 비교할 시간의 기준(단위: 초)
     * @return Long 기준이 되는 시간(단위: 초/분/시)
     */
    private static Long determineDurationTime(Long timeBranch) {
        if (isMinute(timeBranch)) {
            return Math.floorDiv(timeBranch, MINUTE);
        }

        if (isHour(timeBranch)) {
            return Math.floorDiv(timeBranch, HOUR);
        }

        if (isDay(timeBranch)) {
            return Math.floorDiv(timeBranch, DAY);
        }
        return timeBranch;
    }

    private static boolean isMinute(Long timeBranch) {
        return timeBranch >= MINUTE && timeBranch <= HOUR;
    }

    private static boolean isHour(Long timeBranch) {
        return timeBranch >= HOUR && timeBranch <= DAY;
    }

    private static boolean isDay(Long timeBranch) {
        return timeBranch >= DAY;
    }
}

누락 시간 확인

아래 설명을 통하여 전체를 확인.

For문으로 구현

전체 코드

    private static Map<LocalDateTime, Long> findConsecutiveNumbers(List<LocalDateTime> timestamps) {
        Map<LocalDateTime, Long> result = new LinkedHashMap<>();

        for (int i = 0; i < timestamps.size() - 1; i++) {
            if (!timestamps.contains(timestamps.get(i).plusMinutes(1))) {
                LocalDateTime start = timestamps.get(i);
                LocalDateTime end = timestamps.get(i + 1);
                Long count = Duration.between(start, end).toMinutes() - 1;

                result.put(start.plusMinutes(1), count);
            }
        }

        return result;
    }

상세 설명

  1. LinkedHashMap
    삽입이 많이 이뤄지므로 LinkedHashMap으로 진행
    또, 자원이 공유되지 않으므로 멀티 쓰레드를 고려하지 않아도 됨.(ConcurrentHashMap을 고려할 필요 없음)
Map<LocalDateTime, Long> result = new LinkedHashMap<>();
  1. 0 부터 N-1까지 탐색진행
for (int i = 0; i < timestamps.size() - 1; i++)
  1. timeStamps[i]timeStamps[i+1]를 비교하여 연속적이지 않은 경우 추가 로직 진행
if (!timestamps.contains(timestamps.get(i).plusMinutes(1)))
  1. 연속적이지 않은 시간대 추가
  • start.plusMinutes(1) 예를 들어 0분, 1분, 4분 ... 순으로 List에 있었을 경우 2분에 총 2개의 누락이 발생
    • List[1]이 1분 List[2]가 4분이며 두개를 비교할 때 i == 1, List[1] + 1분의 값으로 저장
LocalDateTime start = timestamps.get(i);
result.put(start.plusMinutes(1), count);
  1. 해당 시간 얼마나 누락되었는지 확인
  • Duration.between(start, end).toMinutes() - 1 1분과 4분 사이는 3분이며 총 2분이 누락되었으므로 시간차이의 -1을 저장.
Long count = Duration.between(start, end).toMinutes() - 1;
result.put(start.plusMinutes(1), count);

Collection API로 구현

Collection API도 로직은 크게 변하지 않았으며 코드의 줄이 감소함.

    private static Map<LocalDateTime, Long> findConsecutiveNumbers(List<LocalDateTime> timestamps) {
        Map<LocalDateTime, Long> result = new LinkedHashMap<>();

        IntStream.range(0, timestamps.size() - 1)
                .filter(i -> !timestamps.get(i).plusMinutes(1).equals(timestamps.get(i + 1)))
                .forEach(i -> {
                    LocalDateTime start = timestamps.get(i);
                    LocalDateTime end = timestamps.get(i + 1);
                    long count = Duration.between(start, end).toMinutes() - 1;
                    result.put(start.plusMinutes(1), count);
                });

        return result;
    }
  1. For문은 IntStream.range(0, timestamps.size() - 1)로 구현
  2. if문은 filter(i -> !timestamps.get(i).plusMinutes(1).equals(timestamps.get(i + 1)))로 구현
  3. 누락된 시간대와 누락된 수는 아래 코드 참고
    forEach(i -> {
                    LocalDateTime start = timestamps.get(i);
                    LocalDateTime end = timestamps.get(i + 1);
                    long count = Duration.between(start, end).toMinutes() - 1;
                    result.put(start.plusMinutes(1), count);
                });

결과 값이 정렬되지 않아 정렬 로직 추가

    private static Map<LocalDateTime, Long> findConsecutiveNumbers(List<LocalDateTime> timestamps) {
        Map<LocalDateTime, Long> result = new LinkedHashMap<>();

        int maxTraversal = timestamps.size() - 1;

        IntStream.range(0, maxTraversal)
                .filter(i -> !timestamps.get(i).plusMinutes(1).equals(timestamps.get(i + 1)))
                .forEach(i -> {
                    LocalDateTime start = timestamps.get(i);
                    LocalDateTime end = timestamps.get(i + 1);
                    long count = Duration.between(start, end).toMinutes() - 1;
                    result.put(start.plusMinutes(1), count);
                });

        return result.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (time, count) -> time, LinkedHashMap::new
                ));
    }

result는 LocalDateTime 기준으로 정렬이 필요하여 sorted(Map.Entry.comparingByKey())로 정렬 진행
정렬된 이후 해당 HashMap을 반환해야 함

  • 반환되는 HashMap은 탐색이 필요 없으며 삽입만 많이 이뤄지므로 LinkedHashMap으로 선언)
    • (time, count) -> time, LinkedHashMap::newLinkedHashMap::new

BiFunction으로 중복 최소화

해당 코드는 분만 확인이 가능한 상태.

  1. 초/분/시를 비교하기 위해서는 아래 함수만 변경되며 나머지는 중복
    • plusSeconds(), plusMinutes(), plusHours()
    • getSeconds(), toMinutes(), toHours()
      중복 코드를 최소화하기 위하여 BiFunction으로 구현 진행
    • 함수 사용은 복잡하지 않으며 로직도 단순하여 BiFunction을 사용하여도 가독성을 해치지 않을 것으로 판단

전체 코드

    /**
     * Map<LocalDateTime, Long> 반환
     * 시간/분/초 단위로 누락된 시간의 첫 시간과 해당 Count 확인
     *
     * @param timestamps 전체 시간
     * @param plusFunction LocalDateTime::plusHours, LocalDateTime::plusSeconds, LocalDateTime::plusMinutes
     * @param betweenFunction Duration::toHours, Duration::toMinutes, Duration::getSeconds
     * @return Map<LocalDateTime, Long> : 처음 누락된 시간(LocalDateTime), 시간 단위 별 Count(Long)
     */

    private Map<LocalDateTime, Long> findNonConsecutiveTimes(
            List<LocalDateTime> timestamps,
            BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction,
            BiFunction<Duration, Long, Long> betweenFunction,
            Long durationTime) {

        if(timestamps == null || timestamps.isEmpty()) {
            throw new IllegalArgumentException("Empty time (Stat)");
        }
        if(durationTime == 0) {
            throw new IllegalArgumentException("durationTime is zero(Stat)");
        }

        int maxTraversal = timestamps.size() - 1;

        Map<LocalDateTime, Long> result = IntStream.range(0, maxTraversal)
                .filter(i -> !plusFunction.apply(timestamps.get(i), durationTime).equals(timestamps.get(i + 1)))
                .boxed()
                .collect(Collectors.toMap(
                        i -> plusFunction.apply(timestamps.get(i), durationTime),
                        i -> (
                                betweenFunction.apply(Duration.between(timestamps.get(i), timestamps.get(i + 1)), durationTime)
                        ) - 1
                ));

        return result.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (time, count) -> time, LinkedHashMap::new
                ));
    }
  1. 누락이 언제 발생하였으며 해당 Count를 저장할 임시 변수 Map<LocalDateTime, Long> result

    Map<LocalDateTime, Long> result = IntStream.range(0, maxTraversal)
                .filter(i -> !plusFunction.apply(timestamps.get(i), durationTime).equals(timestamps.get(i + 1)))
                .boxed()
                .collect(Collectors.toMap(
                        i -> plusFunction.apply(timestamps.get(i), durationTime),
                        i -> (
                                betweenFunction.apply(Duration.between(timestamps.get(i), timestamps.get(i + 1)), durationTime)
                        ) - 1
                ));
  2. 저장한 임시변수를 정렬된 이후 반환

        return result.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (time, count) -> time, LinkedHashMap::new
                ));
  3. 입력받은 시간이 초/분/시 중 어디에 해당하는지 확인

    private static final Long SECOND = 1L;
    private static final Long MINUTE = 60L;
    private static final Long HOUR = 3600L;
    private static final Long DAY = 86400L;
    
    private Map<LocalDateTime, Long> findHole(List<LocalDateTime> timestamps, Long timeBranch) {
    
        Map<LocalDateTime, Long> result;
        BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction = LocalDateTime::plusSeconds;
        BiFunction<Duration, Long, Long> toFunction = (duration, ignored) -> duration.getSeconds();
    
        long durationTime = timeBranch;
    
        if (timeBranch >= MINUTE) {
            if(Math.floorMod(timeBranch, MINUTE) != 0) {
                throw new IllegalArgumentException("");
            }
            plusFunction = LocalDateTime::plusMinutes;
            toFunction = (duration, ignored) -> duration.toMinutes();
            durationTime = Math.floorDiv(timeBranch, MINUTE);
        }
    
        if (timeBranch >= HOUR) {
            if(Math.floorMod(timeBranch, HOUR) != 0) {
                throw new IllegalArgumentException("");
            }
            plusFunction = LocalDateTime::plusHours;
            toFunction = (duration, ignored) -> duration.toHours();
            durationTime = Math.floorDiv(timeBranch, HOUR);
        }
    
        if(timeBranch >= DAY) {
            if(Math.floorMod(timeBranch, DAY) != 0) {
                throw new IllegalArgumentException();
            }
            plusFunction = LocalDateTime::plusDays;
            toFunction = (duration, ignored) -> duration.toDays();
            durationTime = Math.floorDiv(timeBranch, DAY);
        }
    
        result = findNonConsecutiveTimes(timestamps, plusFunction, toFunction, durationTime);
    
        return result;
    }

해당 기능을 테스트하기 위한 테스트 함수
전체 코드

import automation.ops.util.TimeFinder;
import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.assertj.core.api.Assertions.assertThat;

public class ConsecutiveTest {
    private static final Long SECOND = 1L;
    private static final Long MINUTE = 60L;
    private static final Long HOUR = 3600L;
    private static final Long DAY = 86400L;

    @Test
    void findConsecutiveMinutes() {
        List<LocalDateTime> minutesTimestamps = Arrays.asList(
                LocalDateTime.parse("2023-08-30T00:12:00"),
                LocalDateTime.parse("2023-08-30T00:13:00"),
                LocalDateTime.parse("2023-08-30T00:14:00"),
                LocalDateTime.parse("2023-08-30T00:24:00"),
                LocalDateTime.parse("2023-08-30T00:25:00"),
                LocalDateTime.parse("2023-08-30T00:26:00"),
                LocalDateTime.parse("2023-08-30T00:28:00")
        );

        List<LocalDateTime> secondTimestamps = Arrays.asList(
                LocalDateTime.parse("2023-08-30T00:10:01"),
                LocalDateTime.parse("2023-08-30T00:10:02"),
                LocalDateTime.parse("2023-08-30T00:10:04"),
                LocalDateTime.parse("2023-08-30T00:10:05"),
                LocalDateTime.parse("2023-08-30T00:10:07"),
                LocalDateTime.parse("2023-08-30T00:10:13"),
                LocalDateTime.parse("2023-08-30T00:10:15")
        );

        Map<LocalDateTime, Long> resultMinutes = findHole(minutesTimestamps, MINUTE);
        Map<LocalDateTime, Long> resultSeconds = findHole(secondTimestamps, SECOND);
        assertThat(resultMinutes.size())
                .isEqualTo(2);

        assertThat(resultMinutes.keySet())
                .contains(LocalDateTime.parse("2023-08-30T00:15"));

        assertThat(resultMinutes.get(LocalDateTime.parse("2023-08-30T00:15")))
                .isEqualTo(9L);

        assertThat(resultMinutes.keySet())
                .contains(LocalDateTime.parse("2023-08-30T00:27"));

        assertThat(resultMinutes.get(LocalDateTime.parse("2023-08-30T00:27")))
                .isEqualTo(1L);

        assertThat(resultSeconds.size())
                .isEqualTo(4);

        assertThat(resultSeconds.keySet())
            .contains(LocalDateTime.parse("2023-08-30T00:10:03"));

        assertThat(resultSeconds.get(LocalDateTime.parse("2023-08-30T00:10:03")))
                .isEqualTo(1L);

        assertThat(resultSeconds.keySet())
                .contains(LocalDateTime.parse("2023-08-30T00:10:06"));

        assertThat(resultSeconds.get(LocalDateTime.parse("2023-08-30T00:10:06")))
                .isEqualTo(1L);

        assertThat(resultSeconds.get(LocalDateTime.parse("2023-08-30T00:10:08")))
                .isEqualTo(5L);

        assertThat(resultSeconds.keySet())
                .contains(LocalDateTime.parse("2023-08-30T00:10:14"));

        assertThat(resultSeconds.get(LocalDateTime.parse("2023-08-30T00:10:14")))
                .isEqualTo(1L);
    }
    private Map<LocalDateTime, Long> findHole(List<LocalDateTime> timestamps, Long timeBranch) {

        Map<LocalDateTime, Long> result;
        BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction = LocalDateTime::plusSeconds;
        BiFunction<Duration, Long, Long> toFunction = (duration, ignored) -> duration.getSeconds();

        long durationTime = timeBranch;

        if (timeBranch >= MINUTE) {
            if(Math.floorMod(timeBranch, MINUTE) != 0) {
                throw new IllegalArgumentException("");
            }
            plusFunction = LocalDateTime::plusMinutes;
            toFunction = (duration, ignored) -> duration.toMinutes();
            durationTime = Math.floorDiv(timeBranch, MINUTE);
        }

        if (timeBranch >= HOUR) {
            if(Math.floorMod(timeBranch, HOUR) != 0) {
                throw new IllegalArgumentException("");
            }
            plusFunction = LocalDateTime::plusHours;
            toFunction = (duration, ignored) -> duration.toHours();
            durationTime = Math.floorDiv(timeBranch, HOUR);
        }

        if(timeBranch >= DAY) {
            if(Math.floorMod(timeBranch, DAY) != 0) {
                throw new IllegalArgumentException();
            }
            plusFunction = LocalDateTime::plusDays;
            toFunction = (duration, ignored) -> duration.toDays();
            durationTime = Math.floorDiv(timeBranch, DAY);
        }

        result = findNonConsecutiveTimes(timestamps, plusFunction, toFunction, durationTime);

        return result;
    }

    /**
     * Map<LocalDateTime, Long> 반환
     * 시간/분/초 단위로 누락된 시간의 첫 시간과 해당 Count 확인
     *
     * @param timestamps 전체 시간
     * @param plusFunction LocalDateTime::plusHours, LocalDateTime::plusSeconds, LocalDateTime::plusMinutes
     * @param betweenFunction Duration::toHours, Duration::toMinutes, Duration::getSeconds
     * @return Map<LocalDateTime, Long> : 처음 누락된 시간(LocalDateTime), 시간 단위 별 Count(Long)
     */

    private Map<LocalDateTime, Long> findNonConsecutiveTimes(
            List<LocalDateTime> timestamps,
            BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction,
            BiFunction<Duration, Long, Long> betweenFunction,
            Long durationTime) {

        if(timestamps == null || timestamps.isEmpty()) {
            throw new IllegalArgumentException("Empty time (Stat)");
        }
        if(durationTime == 0) {
            throw new IllegalArgumentException("durationTime is zero(Stat)");
        }

        int maxTraversal = timestamps.size() - 1;

        Map<LocalDateTime, Long> result = IntStream.range(0, maxTraversal)
                .filter(i -> !plusFunction.apply(timestamps.get(i), durationTime).equals(timestamps.get(i + 1)))
                .boxed()
                .collect(Collectors.toMap(
                        i -> plusFunction.apply(timestamps.get(i), durationTime),
                        i -> (
                                betweenFunction.apply(Duration.between(timestamps.get(i), timestamps.get(i + 1)), durationTime)
                        ) - 1
                ));

        return result.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (time, count) -> time, LinkedHashMap::new
                ));
    }

'개발공부 > JAVA' 카테고리의 다른 글

convert java List to json array String  (0) 2021.12.21
Java Objecto To Json  (0) 2021.12.20