I am working on a project that confuses me really bad right now.
Given is a List<TimeInterval> list that contains elements of the class TimeInterval, which looks like this:
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));
}
The intervals are generated using the following:
TimeInterval tI = new TimeInterval(ld_dateValidFrom, ld_dateValidTo);
The intervals within the list may overlap:
|--------------------|
|-------------------|
This should result in:
|-------||-----------||------|
It should NOT result in:
|--------|-----------|-------|
Generally speaking in numbers:
I1: 2014-01-01 - 2014-01-30
I2: 2014-01-07 - 2014-01-15
That should result in:
I1: 2014-01-01 - 2014-01-06
I2: 2014-01-07 - 2014-01-15
I3: 2014-01-16 - 2014-01-30
I'm using JODA Time API but since I'm using for the first time, I actually don't really have a clue how to solve my problem. I already had a look at the method overlap() / overlapWith() but I still don't get it.
Your help is much appreciated!
UPDATE
I found something similar to my problem >here< but that doesn't help me for now.
I tried it over and over again, and even though it worked for the first intervals I tested, it doesn't actually work the way I wanted it to.
Here are the intervals I have been given:
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
This is the function I am using to generate the new intervals:
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;
}
This is what the output actually looks like:
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
And this is what the output SHOULD look like:
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
Since there is no overlap in the original intervals, I don't get why it produces stuff like
2014-10-26 ---> 2014-10-26
2014-11-02 ---> 2014-11-02
2014-11-09 ---> 2014-11-09
etc
I've been trying to fix this all day long and I'm still not getting there :( Any more help is much appreciated!
Half-Open
I suggest you reconsider the terms of your goal. Joda-Time wisely uses the "Half-Open" approach to defining a span of time. The beginning is inclusive while the ending is exclusive. For example, a week starts an the beginning of the first day and runs up to, but not including, the first moment of the next week. Half-open proves to be quite helpful and natural way to handle spans of time, as discussed in other answers.
Using this Half-Open approach for your example, you do indeed want this result:
|--------|-----------|-------|
I1: 2014-01-01 - 2014-01-07
I2: 2014-01-07 - 2014-01-16
I3: 2014-01-16 - 2014-01-30
Search StackOverflow for "half-open" to find discussion and examples, such as this answer of mine.
Joda-Time Interval
Joda-Time has an excellent Interval class to represent a span of time defined by a pair of endpoints on the timeline. That Interval class offers overlap, overlaps (sic), abuts, and gap methods. Note in particular the overlap method that generates a new Interval when comparing two others; that may be key to your solution.
But unfortunately, that class only works with DateTime objects and not LocalDate (date-only, no time-of-day or time zone). Perhaps that lack of support for LocalDate is why you or your team invented that TimeInterval class. But I suggest rather that using that custom class, consider using DateTime objects with Joda-Time's classes. I'm not 100% certain that is better than rolling your own date-only interval class (I've been tempted to do that), but my gut tells me so.
To focus on days rather than day+time, on your DateTime objects call the withTimeAtStartOfDay method to adjust the time portion to the first moment of the day. That first moment is usually 00:00:00.000 but not necessarily due to Daylight Saving Time (DST) and possibly other anomalies. Just be careful and consistent with the time zone; perhaps use UTC throughout.
Here is some example code in Joda-Time 2.5 using the values suggested in the Question. In these particular lines, the call to withTimeAtStartOfDay may be unnecessary as Joda-Time defaults to first moment of day when no day-of-time is provided. But I suggest using those calls to withTimeAtStartOfDay as it makes your code self-documenting as to your intent. And it makes all your day-focused use of DateTime code consistent.
Interval i1 = new Interval( new DateTime( "2014-01-01", DateTimeZone.UTC ).withTimeAtStartOfDay(), new DateTime( "2014-01-30", DateTimeZone.UTC ).withTimeAtStartOfDay() );
Interval i2 = new Interval( new DateTime( "2014-01-07", DateTimeZone.UTC ).withTimeAtStartOfDay(), new DateTime( "2014-01-15", DateTimeZone.UTC ).withTimeAtStartOfDay() );
From there, apply the logic suggested in the other answers.
Here is a suggested algorithm, based on the answer you have already found. First, you need to sort all the end points of the intervals.
TreeMap<LocalDate,Integer> endPoints = new TreeMap<LocalDate,Integer>();
This map's keys - which are sorted since this is a TreeMap - will be the LocalDate objects at the start and end of your intervals. They are mapped to a number that represents the number of end points at this date subtracted from the number of start points at this date.
Now traverse your list of TimeIntervals. For each one, for the start point, check whether it is already in the map. If so, add one to the Integer. If not, add it to the map with the value of 1.
For the end point of the same interval, if it exists in the map, subtract 1 from the Integer. If not, create it with the value of -1.
Once you finished filling endPoints, create a new list for the "broken up" intervals you will create.
List<TimeInterval> newList = new ArrayList<TimeInterval>();
Now start iterating over endPoints. If you had at least one interval in the original list, you'll have at least two points in endPoints. You take the first, and keep the key (LocalDate) in a variable currStart, and its associated Integer in another variable (curr or something).
Loop starting from the second element until the end. At each iteration:
If curr > 0, create a new TimeInterval starting at currStart and ending at the current key date. Add it to newList.
Add the Integer value to curr.
Assign the key as your next currStart.
And so on until the end.
What happens here is this: ordering the dates makes sure you have no overlaps. Each new interval is guaranteed not to overlap with any new one since they have exclusive and sorted end points. The trick here is to find the spaces in the timeline which are not covered by any intervals at all. Those empty spaces are characterized by the fact that your curr is zero, as it means that all the intervals that started before the current point in time have also ended. All the other "spaces" between the end points are covered by at least one interval so there should be a corresponding new interval in your newList.
Here is an implementation, but please notice that I did not use Joda Time (I don't have it installed at the moment, and there is no particular feature here that requires it). I created my own rudimentary TimeInterval class:
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 + "]";
}
}
The important thing is to add the accessor methods for the start and end to be able to perform the algorithm as I wrote it. In reality, you should probably use Joda's Interval or implement their ReadableInterval if you want to use their extended features.
Now for the method itself. For this to work with yours you'll have to change all Date to 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;
}
(Note that it would probably be more efficient to use a mutable Integer-like object rather than Integer here, but I opted for clarity).
I'm not fully up to speed on Joda; I'll need to read up on that if you want an overlap-specific solution.
However, this is possible using only the dates. This is mostly pseudocode, but should bring the point across. I've also added notation so you can tell what the intervals look like. There's also some confusion for me as to whether I should be adding 1 or subtracting 1 for an overlap, so I erred on the side of caution by pointing outward from the overlap (-1 for start, +1 for end).
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
}
Related
I have a List in the following format and I want to group this List into minute intervals.
List<Item> myObjList = Arrays.asList(
new Item(LocalDateTime.parse("2020-09-22T00:13:36")),
new Item(LocalDateTime.parse("2020-09-22T00:17:20")),
new Item(LocalDateTime.parse("2020-09-22T01:25:20")),
new Item(LocalDateTime.parse("2020-09-18T00:17:20")),
new Item(LocalDateTime.parse("2020-09-19T00:17:20")));
For example, given an interval of 10 minutes the first 2 objects of the list should be in the same group, the 3rd should be in a different group, etc.
Can this List be grouped into intervals using Java's 8 groupingBy function?
My solution is to compare every date in the list with all the other dates in the list and add the dates that differ X minutes in a new List. This seems to be very slow and 'hacky' workaround and I wonder if there is a more stable solution.
It is possible to use Collectors#groupingBy to group LocalDateTime objects into lists of 10-minute intervals. You'll have to adapt this snippet to work with your Item class, but the logic is the same.
List<LocalDateTime> myObjList = Arrays.asList(
LocalDateTime.parse("2020-09-22T00:13:36"),
LocalDateTime.parse("2020-09-22T00:17:20"),
LocalDateTime.parse("2020-09-22T01:25:20"),
LocalDateTime.parse("2020-09-18T00:17:20"),
LocalDateTime.parse("2020-09-19T00:17:20")
);
System.out.println(myObjList.stream().collect(Collectors.groupingBy(time -> {
// Store the minute-of-hour field.
int minutes = time.getMinute();
// Determine how many minutes we are above the nearest 10-minute interval.
int minutesOver = minutes % 10;
// Truncate the time to the minute field (zeroing out seconds and nanoseconds),
// and force the number of minutes to be at a 10-minute interval.
return time.truncatedTo(ChronoUnit.MINUTES).withMinute(minutes - minutesOver);
})));
Output
{
2020-09-22T00:10=[2020-09-22T00:13:36, 2020-09-22T00:17:20],
2020-09-19T00:10=[2020-09-19T00:17:20],
2020-09-18T00:10=[2020-09-18T00:17:20],
2020-09-22T01:20=[2020-09-22T01:25:20]
}
You didn't specify the key for the groups so I just used the quotient of (minutes/10)*10 to get the start of the range of minutes tagged onto the time truncated to hours.
List<Item> myObjList = Arrays.asList(
new Item(LocalDateTime.parse("2020-09-22T00:13:36")),
new Item(LocalDateTime.parse("2020-09-22T00:17:20")),
new Item(LocalDateTime.parse("2020-09-22T01:25:20")),
new Item(LocalDateTime.parse("2020-09-18T00:17:20")),
new Item(LocalDateTime.parse("2020-09-19T00:17:20")));
Map<String, List<Item>> map = myObjList.stream()
.collect(Collectors.groupingBy(item -> {
int range =
(item.getTime().getMinute() / 10) * 10;
return item.getTime()
.truncatedTo(ChronoUnit.HOURS).plusMinutes(range) +
" - " + (range + 9) + ":59";
}));
map.entrySet().forEach(System.out::println);
Prints
2020-09-22T00:10 - 19:59=[2020-09-22T00:13:36, 2020-09-22T00:17:20]
2020-09-19T00:10 - 19:59=[2020-09-19T00:17:20]
2020-09-18T00:10 - 19:59=[2020-09-18T00:17:20]
2020-09-22T01:20 - 29:59=[2020-09-22T01:25:20]
Here is the class I used.
class Item {
LocalDateTime ldt;
public Item(LocalDateTime ldt) {
this.ldt = ldt;
}
public String toString() {
return ldt.toString();
}
public LocalDateTime getTime() {
return ldt;
}
}
I'm currently developing some functionality that needs to either subtract or add time to a Calendar class instance. The time I need to add/sub is in a properties file and could be any of these formats:
30,sec
90,sec
1.5,min
2,day
2.333,day
Let's assume addition for simplicity. I would read those values in a String array:
String[] propertyValues = "30,sec".split(",");
I would read the second value in that comma-separated pair, and map that to the relevant int in the Calendar class (so for example, "sec" becomes Calendar.SECOND, "min" becomes Calendar.MINUTE):
int calendarMajorModifier = mapToCalendarClassIntValues(propertyValues[1]);
To then do the actual operation I would do it as simple as:
cal.add(calendarMajorModifier, Integer.parseInt(propertyValues[0]));
This works and it's not overly complicated. The issue is now floating values (so 2.333,day for eaxmple) - how would you deal with it?
String[] propertyValues = "2.333,day".split(",");
As you can imagine the code becomes quite hairy (I haven't actually written it yet, so please ignore syntax mistakes)
float timeComponent = Float.parseFloat(propertyValues[0]);
if (calendarMajorModifier == Calendar.DATE) {
int dayValue = Integer.parseFloat(timeComponent);
cal.add(calendarMajorModifier, dayValue);
timeComponent = (timeComponent - dayValue) * 24; //Need to convert a fraction of a day to hours
if (timeComponent != 0) {
calendarMajorModifier = Calendar.HOUR;
}
}
if (calendarMajorModifier == Calendar.HOUR) {
int hourValue = Integer.parseFloat(timeComponent);
cal.add(calendarMajorModifier, hourValue);
timeComponent = (timeComponent - hourValue) * 60; //Need to convert a fraction of an hour to minutes
if (timeComponent != 0) {
calendarMajorModifier = Calendar.MINUTE;
}
}
... etc
Granted, I can see how there may be a refactoring opportunity, but still seems like a very brute-forceish solution.
I am using the Calendar class to do the operations on but could technically be any class. As long as I can convert between them (i.e. by getting the long value and using that), as the function needs to return a Calendar class. Ideally the class also has to be Java native to avoid third party licensing issues :).
Side note: I suggested changing the format to something like yy:MM:ww:dd:hh:mm:ss to avoid floating values but that didn't pan out. I also suggested something like 2,day,5,hour, but again, ideally needs to be format above.
I'd transform the value into the smallest unit and add that:
float timeComponent = Float.parseFloat(propertyValues[0]);
int unitFactor = mapUnitToFactor(propertyValues[1]);
cal.add(Calendar.SECOND, (int)(timeComponent * unitFactor));
and mapUnitToFactor would be something like:
int mapUnitToFactor(String unit)
{
if ("sec".equals(unit))
return 1;
if ("min".equals(unit))
return 60;
if ("hour".equals(unit))
return 3600;
if ("day".equals(unit))
return 24*3600;
throw new InvalidParameterException("Unknown unit: " + unit);
}
So for example 2.333 days would be turned into 201571 seconds.
I have problem with aggregating data based on their timestamp per day for a timespan of one week. There is a SQLite database, which has a table which I save the number of walking steps in (timestamp column is UTC and created_At is local time, but I don't use the created_at column anyway).
What I want to do is get the total data which happened in 7 days ago until the midnight of a day before. So I have this jodatime expression to find start and end for timestamps
long start = new DateTime().withMillisOfDay(0).minusDays(7).getMillis();
long end = new DateTime().withTimeAtStartOfDay().getMillis();
//start milli:1405029600000 DateTime: 2014-07-11 00:00:00
//end milli:1405634400000 DateTime: 2014-07-18 00:00:00
Then I execute this sql command:
SELECT * FROM pa_data WHERE timestamp BETWEEN 1405029600000 AND 1405634400000
And I am pretty sure that it returns the correct rows ( I have compared the android database result with SQLite Database Browser on my pc, both return same number of rows). For this, I tried to use this nested iteration:
the Object I am trying to create is:
public class PhysicalActivityPerDay {
private List<PhysicalActivity> mList;
public PhysicalActivityPerDay(List<PhysicalActivity> list) {
mList = new ArrayList<PhysicalActivity>(list);
}
//methods....
}
Now the problem is, I want to have a data object that can hold the rows for each day.
List<PhysicalActivity> all = getPhysicalActivitiesBetween(start, end);
List<PhysicalActivityPerDay> perDays = new ArrayList<PhysicalActivityPerDay>();
List<PhysicalActivity> tempList;
PhysicalActivityPerDay tempPerDay;
for (int i = 0; i < 7; i++) {
long begin = start;
long stop = (begin + 86400000); //add 24 hours
tempList = new ArrayList<PhysicalActivity>();
for (int j = 0; j < all.size(); j++) {
PhysicalActivity p = all.get(j);
DateTime when = new DateTime(p.getTimestamp());
if (when.isAfter(start) && when.isBefore(stop)) {
tempList.add(p);
all.remove(j); //remove the matching object from the list
}
}
tempPerDay = new PhysicalActivityPerDay(tempList);
perDays.add(tempPerDay);
start += 86400000; //add 24 hours or 1 day for next iteration
}
return perDays;
But the result is totally unexpected. There are many rows which don't match the if statements above. I did a debug and here is what happens:
Log.w(TAG, "There are totally " + all.size() + " physical activities for day for 7 days");
//There are totally 6559 physical activities for day for 7 days
But, when I check the all list (total rows returned by DB) although I am removing matched objects from it, if I query its size after the nested iteration, it surprisingly still contains many objects in it, telling me that the iteration was not successful!
//Remaining: 3278 records after iterations from 6559
What I am doing wrong? please help me findout!
Not sure if that's the only problem :
You are looping over the all List, and removing items.
When you call all.remove(j), the item that used to be at position j+1 moves to poisition j. Which means your for loop would skip that item.
One way to solve this is to increment j only if you don't remove an item from the list.
for (int j = 0; j < all.size();) {
PhysicalActivity p = all.get(j);
DateTime when = new DateTime(p.getTimestamp());
if (when.isAfter(start) && when.isBefore(stop)) {
tempList.add(p);
all.remove(j); //remove the matching object from the list
} else {
j++;
}
}
Actually, I'm not entirely sure if the loop would work after this fix. It depends whether all.size() is evaluated in each iteration. If it isn't, it would expect the list to have the initial number of elements, even though you are removing items. In that case you can expect to get an exception the first time you try to access an index beyond the last index of the array.
If you get an exception, you can replace the loop with a while loop :
Iterator<PhysicalActivity> iter = all.iterator();
while (iter.hasNext ()) {
PhysicalActivity p = iter.next();
...
if (...) {
iter.remove();
}
}
Refer to the definition of List.remove() :
public E remove(int index)
Removes the element at the specified position in this list. Shifts any subsequent elements to the left (subtracts one from their indices).
How about letting SQL perform your aggregation for you
SELECT strftime('%W-%Y',dt) as weekYear, count(1) as occurencePerWeek
FROM SOMETABLE c GROUP BY weekYear;
http://sqlfiddle.com/#!5/e63c3/1
I am a begginer at jess rules so i can't understand how i could use it. I had read a lot of tutorials but i am confused.
So i have this code :
Date choosendate = "2013-05-05";
Date date1 = "2013-05-10";
Date date2 = "2013-05-25";
Date date3 = "2013-05-05";
int var = 0;
if (choosendate.compareTo(date1)==0)
{
var = 1;
}
else if (choosendate.compareTo(date2)==0)
{
var = 2;
}
else if (choosendate.compareTo(date3)==0)
{
var = 3;
}
How i could do it with jess rules?
I would like to make a jess rules who takes the dates , compare them and give me back in java the variable var. Could you make me a simple example to understand it?
This problem isn't a good fit for Jess as written (the Java code is short and efficient as-is) but I can show you a solution that could be adapted to other more complex situations. First, you would need to define a template to hold Date, int pairs:
(deftemplate pair (slot date) (slot score))
Then you could create some facts using the template. These are somewhat equivalent to your date1, date2, etc, except they associate each date with the corresponding var value:
(import java.util.Date)
(assert (pair (date (new Date 113 4 10)) (score 1)))
(assert (pair (date (new Date 113 4 25)) (score 2)))
(assert (pair (date (new Date 113 4 5)) (score 3)))
We can define a global variable to hold the final, computed score (makes it easier to get from Java.) This is the equivalent of your var variable:
(defglobal ?*var* = 0)
Assuming that the "chosen date" is going to be in an ordered fact chosendate, we could write a rule like the following. It replaces your chain of if statements, and will compare your chosen date to all the dates in working memory until it finds a match, then stop:
(defrule score-date
(chosendate ?d)
(pair (date ?d) (score ?s))
=>
(bind ?*var* ?s)
(halt))
OK, now, all the code above goes in a file called dates.clp. The following Java code will make use of it (the call to Rete.watchAll() is included so you can see some interesting trace output; you'd leave that out in a real program):
import jess.*;
// ...
// Get Jess ready
Rete engine = new Rete();
engine.batch("dates.clp");
engine.watchAll();
// Plug in the "chosen date"
Date chosenDate = new Date(113, 4, 5);
Fact fact = new Fact("chosendate", engine);
fact.setSlotValue("__data", new Value(new ValueVector().add(chosenDate), RU.LIST));
engine.assertFact(fact);
// Run the rule and report the result
int count = engine.run();
if (count > 0) {
int score = engine.getGlobalContext().getVariable("*var*").intValue(null);
System.out.println("Score = " + score);
} else {
System.out.println("No matching date found.");
}
As I said, this isn't a great fit, because the resulting code is larger and more complex than your original. Where using a rule engine makes sense is if you've got multiple rules that interact; such a Jess program has no more overhead than this, and so fairly quickly starts to look like a simplification compared to equivalent Java code. Good luck with Jess!
I have list of Joda-Time intervals
List<Interval> intervals = new ArrayList<Interval>();
and another Joda-Time interval (search time interval), like on the picture below.
I need to write Java function that finds the holes in time and returns List<Interval> with the red intervals.
Building up on fge's response - the following version actually handles both cases (when the big interval is larger than the extremes of the intervals being searched over + the case when the big interval is in fact smaller ... or smaller on one side)
you can see the full code along with the tests at https://github.com/erfangc/JodaTimeGapFinder.git
public class DateTimeGapFinder {
/**
* Finds gaps on the time line between a list of existing {#link Interval}
* and a search {#link Interval}
*
* #param existingIntervals
* #param searchInterval
* #return The list of gaps
*/
public List<Interval> findGaps(List<Interval> existingIntervals, Interval searchInterval) {
List<Interval> gaps = new ArrayList<Interval>();
DateTime searchStart = searchInterval.getStart();
DateTime searchEnd = searchInterval.getEnd();
if (hasNoOverlap(existingIntervals, searchInterval, searchStart, searchEnd)) {
gaps.add(searchInterval);
return gaps;
}
// create a sub-list that excludes interval which does not overlap with
// searchInterval
List<Interval> subExistingList = removeNoneOverlappingIntervals(existingIntervals, searchInterval);
DateTime subEarliestStart = subExistingList.get(0).getStart();
DateTime subLatestStop = subExistingList.get(subExistingList.size() - 1).getEnd();
// in case the searchInterval is wider than the union of the existing
// include searchInterval.start => earliestExisting.start
if (searchStart.isBefore(subEarliestStart)) {
gaps.add(new Interval(searchStart, subEarliestStart));
}
// get all the gaps in the existing list
gaps.addAll(getExistingIntervalGaps(subExistingList));
// include latestExisting.stop => searchInterval.stop
if (searchEnd.isAfter(subLatestStop)) {
gaps.add(new Interval(subLatestStop, searchEnd));
}
return gaps;
}
private List<Interval> getExistingIntervalGaps(List<Interval> existingList) {
List<Interval> gaps = new ArrayList<Interval>();
Interval current = existingList.get(0);
for (int i = 1; i < existingList.size(); i++) {
Interval next = existingList.get(i);
Interval gap = current.gap(next);
if (gap != null)
gaps.add(gap);
current = next;
}
return gaps;
}
private List<Interval> removeNoneOverlappingIntervals(List<Interval> existingIntervals, Interval searchInterval) {
List<Interval> subExistingList = new ArrayList<Interval>();
for (Interval interval : existingIntervals) {
if (interval.overlaps(searchInterval)) {
subExistingList.add(interval);
}
}
return subExistingList;
}
private boolean hasNoOverlap(List<Interval> existingIntervals, Interval searchInterval, DateTime searchStart, DateTime searchEnd) {
DateTime earliestStart = existingIntervals.get(0).getStart();
DateTime latestStop = existingIntervals.get(existingIntervals.size() - 1).getEnd();
// return the entire search interval if it does not overlap with
// existing at all
if (searchEnd.isBefore(earliestStart) || searchStart.isAfter(latestStop)) {
return true;
}
return false;
}
}
A quick look at the Interval API gives this (UNTESTED):
// SUPPOSED: the big interval is "bigInterval"; the list is "intervals"
// Intervals returned
List<Interval> ret = new ArrayList<>();
Interval gap, current, next;
// First, compute the gaps between the elements in the list
current = intervals.get(0);
for (int i = 1; i < intervals.size(); i++) {
next = intervals.get(i);
gap = current.gap(next);
if (gap != null)
ret.add(gap);
current = next;
}
// Now, compute the time difference between the starting time of the first interval
// and the starting time of the "big" interval; add it at the beginning
ReadableInstant start, end;
start = bigInterval.getStart();
end = intervals.get(0).getStart();
if (start.isBefore(end))
ret.add(0, new Interval(start, end));
//
// finally, append the time difference between the ending time of the last interval
// and the ending time of the "big" interval
// next still contains the last interval
start = next.getEnd();
end = bigInterval.getEnd();
if (start.isBefore(end))
ret.add(new Interval(start, end));
return ret;
The answer by fge seems to be correct, though I've not run that untested code.
The term "gap" seems to be a more common term for what you are calling "holes".
See this answer by Katja Christiansen, which makes good use of the gap method on the Interval class.
Interval gapInterval = interval_X.gap( interval_Y );
// … Test for null to see whether or a gap exists.
If there is a non-zero duration between them, you get a new Interval object returned. If the intervals overlap or abut, then null is returned. Note that the Interval class also offers the methods overlap and abuts if you are interested in those particular conditions.
Of course your collection of Interval objects must be sorted for this to work.