我正在制作一个令我困惑的项目。
给定List<TimeInterval> list
包含类TimeInterval
的元素,如下所示:
public class TimeInterval {
private static final Instant CONSTANT = new Instant(0);
private final LocalDate validFrom;
private final LocalDate validTo;
public TimeInterval(LocalDate validFrom, LocalDate validTo) {
this.validFrom = validFrom;
this.validTo = validTo;
}
public boolean isValid() {
try {
return toInterval() != null;
}
catch (IllegalArgumentException e) {
return false;
}
}
public boolean overlapsWith(TimeInterval timeInterval) {
return this.toInterval().overlaps(timeInterval.toInterval());
}
private Interval toInterval() throws IllegalArgumentException {
return new Interval(validFrom.toDateTime(CONSTANT), validTo.toDateTime(CONSTANT));
}
使用以下内容生成间隔:
TimeInterval tI = new TimeInterval(ld_dateValidFrom, ld_dateValidTo);
列表中的间隔可能重叠:
|--------------------|
|-------------------|
这应该导致:
|-------||-----------||------|
NOT 会导致:
|--------|-----------|-------|
一般来说,数字:
I1: 2014-01-01 - 2014-01-30
I2: 2014-01-07 - 2014-01-15
这应该导致:
I1: 2014-01-01 - 2014-01-06
I2: 2014-01-07 - 2014-01-15
I3: 2014-01-16 - 2014-01-30
我正在使用 JODA Time API ,但由于我第一次使用,我实际上并不知道如何解决我的问题。我已经看过方法overlap() / overlapWith()
,但我仍然没有得到它。
非常感谢您的帮助!
更新 我找到了类似于我的问题的>here<,但这对我现在没有帮助。
我一遍又一遍地尝试过,虽然它在我测试的第一个时间间隔内有效,但它实际上并没有按照我想要的方式工作。
以下是我给出的时间间隔:
2014-10-20 ---> 2014-10-26
2014-10-27 ---> 2014-11-02
2014-11-03 ---> 2014-11-09
2014-11-10 ---> 2014-11-16
2014-11-17 ---> 9999-12-31
这是我用来生成新间隔的函数:
private List<Interval> cleanIntervalList(List<Interval> sourceList) {
TreeMap<DateTime, Integer> endPoints = new TreeMap<DateTime, Integer>();
// Fill the treeMap from the TimeInterval list. For each start point,
// increment the value in the map, and for each end point, decrement it.
for (Interval interval : sourceList) {
DateTime start = interval.getStart();
if (endPoints.containsKey(start)) {
endPoints.put(start, endPoints.get(start)+1);
}
else {
endPoints.put(start, 1);
}
DateTime end = interval.getEnd();
if (endPoints.containsKey(end)) {
endPoints.put(end, endPoints.get(start)-1);
}
else {
endPoints.put(end, 1);
}
}
System.out.println(endPoints);
int curr = 0;
DateTime currStart = null;
// Iterate over the (sorted) map. Note that the first iteration is used
// merely to initialize curr and currStart to meaningful values, as no
// interval precedes the first point.
List<Interval> targetList = new LinkedList<Interval>();
for (Entry<DateTime, Integer> e : endPoints.entrySet()) {
if (curr > 0) {
if (e.getKey().equals(endPoints.lastEntry().getKey())){
targetList.add(new Interval(currStart, e.getKey()));
}
else {
targetList.add(new Interval(currStart, e.getKey().minusDays(1)));
}
}
curr += e.getValue();
currStart = e.getKey();
}
System.out.println(targetList);
return targetList;
}
这就是输出实际上的样子:
2014-10-20 ---> 2014-10-25
2014-10-26 ---> 2014-10-26
2014-10-27 ---> 2014-11-01
2014-11-02 ---> 2014-11-02
2014-11-03 ---> 2014-11-08
2014-11-09 ---> 2014-11-09
2014-11-10 ---> 2014-11-15
2014-11-16 ---> 2014-11-16
2014-11-17 ---> 9999-12-31
这就是输出应该的样子:
2014-10-20 ---> 2014-10-26
2014-10-27 ---> 2014-11-02
2014-11-03 ---> 2014-11-09
2014-11-10 ---> 2014-11-16
2014-11-17 ---> 9999-12-31
由于原始区间没有重叠,我不明白为什么会产生像
这样的东西2014-10-26 ---> 2014-10-26
2014-11-02 ---> 2014-11-02
2014-11-09 ---> 2014-11-09
etc
我一直在努力解决这个问题,但我仍然没有到达那里:(非常感谢任何帮助!
答案 0 :(得分:5)
我建议你重新考虑你的目标条款。 Joda-Time明智地使用&#34;半开放&#34;定义时间跨度的方法。开头是包含,而结尾是独占。例如,一周开始于第一天的开始,并在下周的第一个时刻运行,但不包括。半开放被证明是处理时间跨度的非常有用和自然的方式,正如其他答案中所讨论的那样。
在您的示例中使用此半开放方法,您确实需要此结果:
|--------|-----------|-------|
I1: 2014-01-01 - 2014-01-07
I2: 2014-01-07 - 2014-01-16
I3: 2014-01-16 - 2014-01-30
搜索StackOverflow&#34;半开&#34;找到讨论和例子,例如我的this answer。
Joda-Time有一个很好的Interval类来表示时间轴上由一对端点定义的时间跨度。 Interval课程提供overlap
,overlaps
(原文如此),abuts
和gap
方法。特别要注意的是overlap
方法,它在比较其他两个时生成一个新的Interval;这可能是您解决方案的关键。
但不幸的是,该课程仅适用于DateTime
个对象,而不适用于LocalDate
(仅限日期,没有时间或时区)。也许缺乏对LocalDate的支持是您或您的团队发明TimeInterval
课程的原因。但我建议使用该自定义类,考虑将DateTime对象与Joda-Time的类一起使用。我不是百分之百肯定比滚动你自己的日期间隔班更好(我很想做到这一点),但我的直觉告诉了我。
要关注日期而不是日期+时间,请在DateTime
个对象上调用withTimeAtStartOfDay
方法将时间部分调整为当天的第一时刻。第一个时刻通常是00:00:00.000
,但不一定是由于夏令时(DST)和可能的其他异常。要小心并与时区保持一致;也许整个使用UTC。
以下是使用问题中建议的值在Joda-Time 2.5中的一些示例代码。在这些特定行中,对withTimeAtStartOfDay
的调用可能是不必要的,因为Joda-Time默认为没有提供日期的第一时刻。但我建议使用withTimeAtStartOfDay
这些调用,因为它会使您的代码自我记录您的意图。它使您{J}代码的所有日常使用都保持一致。
DateTime
从那里,应用其他答案中建议的逻辑。
答案 1 :(得分:2)
以下是基于您已找到的答案的建议算法。首先,您需要对间隔的所有端点进行排序。
TreeMap<LocalDate,Integer> endPoints = new TreeMap<LocalDate,Integer>();
此地图的键 - 由于这是一个TreeMap而排序 - 将是间隔开始和结束时的LocalDate对象。它们被映射到一个数字,该数字表示此日期的终点数量,从该日期的起点数量中减去。
现在遍历您的TimeInterval
列表。对于每一个,对于起点,检查它是否已经在地图中。如果是这样,请在Integer中添加一个。如果没有,请将其添加到值为1的地图中。
对于相同间隔的终点,如果它存在于地图中,则从整数中减去1。如果没有,请使用值-1创建它。
填完endPoints
后,为您要创建的“分解”区间创建一个新列表。
List<TimeInterval> newList = new ArrayList<TimeInterval>();
现在开始迭代endPoints
。如果原始列表中至少有一个间隔,则endPoints
中至少有两个点。你拿第一个,并将密钥(LocalDate)保存在变量currStart
中,并将其关联的Integer保存在另一个变量(curr
或其他东西)中。
循环从第二个元素开始直到结束。在每次迭代时:
curr > 0
,请从TimeInterval
开始创建一个新的currStart
,并在当前关键日期结束。将其添加到newList
。curr
。currStart
。等到最后。
这里发生的是:订购日期确保您没有重叠。保证每个新间隔不与任何新间隔重叠,因为它们具有独占和排序的端点。这里的技巧是在时间线中找到任何间隔都没有覆盖的空间。那些空白空间的特点是你的curr
为零,这意味着在当前时间点之前开始的所有间隔也已经结束。端点之间的所有其他“空格”至少包含一个时间间隔,因此newList
中应该有相应的新间隔。
这是一个实现,但请注意我没有使用Joda Time(我目前没有安装它,并且这里没有特殊功能需要它)。我创建了自己的基础TimeInterval
类:
public class TimeInterval {
private final Date validFrom;
private final Date validTo;
public TimeInterval(Date validFrom, Date validTo) {
this.validFrom = validFrom;
this.validTo = validTo;
}
public Date getStart() {
return validFrom;
}
public Date getEnd() {
return validTo;
}
@Override
public String toString() {
return "[" + validFrom + " - " + validTo + "]";
}
}
重要的是为开始和结束添加访问器方法,以便能够在我编写时执行算法。实际上,如果你想使用他们的扩展功能,你应该使用Joda的Interval
或实现他们的ReadableInterval
。
现在为方法本身。为了与您合作,您必须将所有Date
更改为LocalDate
:
public static List<TimeInterval> breakOverlappingIntervals( List<TimeInterval> sourceList ) {
TreeMap<Date,Integer> endPoints = new TreeMap<>();
// Fill the treeMap from the TimeInterval list. For each start point, increment
// the value in the map, and for each end point, decrement it.
for ( TimeInterval interval : sourceList ) {
Date start = interval.getStart();
if ( endPoints.containsKey(start)) {
endPoints.put(start, endPoints.get(start) + 1);
} else {
endPoints.put(start, 1);
}
Date end = interval.getEnd();
if ( endPoints.containsKey(end)) {
endPoints.put(end, endPoints.get(start) - 1);
} else {
endPoints.put(end, -1);
}
}
int curr = 0;
Date currStart = null;
// Iterate over the (sorted) map. Note that the first iteration is used
// merely to initialize curr and currStart to meaningful values, as no
// interval precedes the first point.
List<TimeInterval> targetList = new ArrayList<>();
for ( Map.Entry<Date,Integer> e : endPoints.entrySet() ) {
if ( curr > 0 ) {
targetList.add(new TimeInterval(currStart, e.getKey()));
}
curr += e.getValue();
currStart = e.getKey();
}
return targetList;
}
(请注意,在这里使用类似Integer的可变对象而不是Integer可能更有效,但我选择了清晰度。)
答案 2 :(得分:0)
我没有完全掌握Joda的速度;如果你想要一个特定于重叠的解决方案,我需要阅读它。
但是,仅使用日期即可。这主要是伪代码,但应该说明问题。我还添加了符号,以便您可以分辨出间隔的样子。对于我是否应该为重叠添加1或减1来我也有一些困惑,所以我通过从重叠向外指向而小心谨慎(-1表示开始,+1表示结束)
TimeInterval a, b; //a and b are our two starting intervals
TimeInterval c = null;; //in case we have a third interval
if(a.start > b.start) { //move the earliest interval to a, latest to b, if necessary
c = a;
a = b;
b = c;
c = null;
}
if(b.start > a.start && b.start < a.end) { //case where b starts in the a interval
if(b.end > a.end) { //b ends after a |AA||AB||BB|
c = new TimeInterval(a.end + 1, b.end);//we need time interval c
b.end = a.end;
a.end = b.start - 1;
}
else if (b.end < a.end) { //b ends before a |AA||AB||AA|
c = new TimeInterval(b.end + 1, a.end);//we need time interval c
a.end = b.start - 1;
}
else { //b and a end at the same time, we don't need c |AA||AB|
c = null;
a.end = b.start - 1;
}
}
else if(a.start == b.start) { //case where b starts same time as a
if(b.end > a.end) { //b ends after a |AB||B|
b.start = a.end + 1;
a.end = a.end;
}
else if(b.end < a.end) { //b ends before a |AB||A|
b.start = b.end + 1;
b.end = a.end;
a.end = b.start;
}
else { //b and a are the same |AB|
b = null;
}
}
else {
//no overlap
}