我试图计算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 - 我很高兴地假设第一个时期从时间间隔的开始开始 - 否则我怀疑答案可能会有所不同,具体取决于剩余时间是在开始时间,结尾时间还是两者之间。 / p>
答案 0 :(得分: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;
}
}
答案 1 :(得分:0)
我已将二进制搜索解决方案的开头放在此处:https://gist.github.com/Mahoney/9899936
它比线性搜索复杂得多;另一方面,它在1000年内找到月数的速度大约快了100倍。
它还没有完成 - 更多的是实验而不是任何事情,所以我确定有未经测试的边缘情况(负面时期?)。
仍然很想知道是否有人有更优雅的解决方案(或者只是我不需要编写,测试和维护!)。