【问题标题】:Number of Periods in an Interval in Joda TimeJoda时间间隔中的周期数
【发布时间】:2014-04-03 20:36:34
【问题描述】:

我正在尝试计算 Joda 时间间隔中完整连续周期的数量(其中周期是任意但恒定的)。

我想出的简单解决方案是使用 while 循环进行线性搜索:

public static long periodsInAnInterval(Interval interval, Period period) {
    int periods = -1;
    DateTime marker = interval.getStart();
    while (marker.isBefore(interval.getEnd()) || marker.isEqual(interval.getEnd())) {
        marker = marker.plus(period);
        periods++;
    }
    return periods;
}

O(n) 的解决方案显然非常可怕,所以有人能想到更好的方法吗?我想知道是否可以使用某种二进制搜索...

这是一个测试用例:https://gist.github.com/Mahoney/9899832

编辑 - 记住句点没有已知的秒数; Period.toStandardDuration() 只是一个近似值,假设年有 365 天,月有 30 天,天有 24 小时。 (实际上,如果您在该期间有数年或数月,则快速测试会显示 Period.toStandardDuration 炸弹除外。)

编辑 2 - 我很高兴假设第一个时段从间隔开始 - 否则我怀疑答案可能会有所不同,具体取决于剩余时间是在开始、结束还是两者兼而有之。

【问题讨论】:

  • 获取longs 并进行整数除法?
  • 所以我认为应该可以将 Period 转换为最大和最小毫秒数。将区间中的毫秒除以这些中的每一个可以给出二进制搜索的上限和下限以找到周期数。

标签: java jodatime


【解决方案1】:

这是我的首选解决方案:使用周期的平均长度来形成最佳猜测,然后对其进行改进。这似乎是最有效和最优雅的方式。

import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import org.joda.time.*;

import static com.google.common.collect.FluentIterable.from;
import static java.util.Arrays.asList;
import static org.joda.time.DurationFieldType.*;

public class PeriodArithmetic {

    public static long periodsInAnInterval(Interval interval, Period period) {
        int bestGuess = (int) (interval.toDurationMillis() / toAverageMillis(period));
        if (bestGuess < 0) return 0;
        if (startPlusScaledPeriodIsAfterEnd(interval, period, bestGuess + 1)) {
            return searchDownwards(interval, period, bestGuess);
        } else {
            return searchUpwards(interval, period, bestGuess);
        }
    }

    private static long searchDownwards(Interval interval, Period period, int currentGuess) {
        if (startPlusScaledPeriodIsAfterEnd(interval, period, currentGuess)) {
            return searchDownwards(interval, period, currentGuess - 1);
        } else {
            return currentGuess;
        }
    }

    private static long searchUpwards(Interval interval, Period period, int currentGuess) {
        if (!startPlusScaledPeriodIsAfterEnd(interval, period, currentGuess + 1)) {
            return searchUpwards(interval, period, currentGuess + 1);
        } else {
            return currentGuess;
        }
    }

    private static boolean startPlusScaledPeriodIsAfterEnd(Interval interval, Period period, int scalar) {
        return interval.getStart().plus(period.multipliedBy(scalar)).isAfter(interval.getEnd());
    }

    private static final long MILLIS_IN_DAY = Days.ONE.toStandardSeconds().getSeconds() * 1000L;
    private static final long MILLIS_IN_YEAR = Days.ONE.toStandardSeconds().getSeconds() * 365250L;

    private static final ImmutableMap<DurationFieldType, Long> averageLengthMillis
            = ImmutableMap.<DurationFieldType, Long>builder()
            .put(millis(),    1L)
            .put(seconds(),   1000L)
            .put(minutes(),   Minutes.ONE.toStandardSeconds().getSeconds() * 1000L)
            .put(hours(),     Hours.ONE.toStandardSeconds().getSeconds() * 1000L)
            .put(halfdays(),  MILLIS_IN_DAY / 2)
            .put(days(),      MILLIS_IN_DAY)
            .put(weeks(),     Weeks.ONE.toStandardSeconds().getSeconds() * 1000L)
            .put(months(),    MILLIS_IN_YEAR / 12)
            .put(years(),     MILLIS_IN_YEAR)
            .put(weekyears(), MILLIS_IN_YEAR)
            .put(centuries(), MILLIS_IN_YEAR * 100)
            .put(eras(),      Long.MAX_VALUE)
            .build();

    private static long toAverageMillis(Period period) {
        final Iterable<Long> milliValues = from(asList(period.getFieldTypes())).transform(toAverageMillisForFieldType(period));
        return total(milliValues);
    }

    private static Function<DurationFieldType, Long> toAverageMillisForFieldType(final Period period) {
        return new Function<DurationFieldType, Long>() {
            @Override
            public Long apply(DurationFieldType durationFieldType) {
                final Long averageDuration = averageLengthMillis.get(durationFieldType);
                return period.get(durationFieldType) * averageDuration;
            }
        };
    }

    private static long total(Iterable<Long> milliValues) {
        long acc = 0;
        for (Long milliValue : milliValues) {
            acc += milliValue;
        }
        return acc;
    }
}

【讨论】:

    【解决方案2】:

    我已将二进制搜索解决方案的开头放在此处:https://gist.github.com/Mahoney/9899936

    它比线性搜索要复杂得多;另一方面,例如,查找 1000 年中的月数的速度大约快 100 倍。

    它也未完成 - 更多的是一个实验,所以我确信存在未经测试的边缘情况(负数?)。

    仍然很想知道是否有人有更优雅的解决方案(或者只有一个我不必编写、测试和维护的解决方案!)。

    【讨论】:

      猜你喜欢
      • 2011-05-22
      • 1970-01-01
      • 1970-01-01
      • 2012-08-15
      • 2013-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多