MongoDB aggregate variation to match on days difference match - java

What could be variation of this aggregation to get the result or items where the difference of the days = (a number) and the $acceptedDate is greater than $liveDate - x days
AggregationOperation redact = new AggregationOperation() {
#Override
public DBObject toDBObject(AggregationOperationContext aggregationOperationContext) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("if", BasicDBObject.parse("{'$gte':[{'$subtract':[{'$ifNull':['$acceptedDate',
{'$date':" + System.currentTimeMillis() + "}]},'$lastVisit' **here minus x days **]},1296000000]}}")); //and the difference is 10 days
map.put("then", "$$KEEP");
map.put("else", "$$PRUNE");
return new BasicDBObject("$redact", new BasicDBObject("$cond", map));
};
Aggregation aggregation = Aggregation.newAggregation(redact);
List<FactoryAcceptance> results = mongoTemplate.aggregate(aggregation, FactoryAcceptance.class, FactoryAcceptance.class).getMappedResults();
Basically a parameter is being passed as a variation for this where they want to match the difference in days (say show me records where the difference is 5 days), but in this case lastVisit should be minus 5 days.

You can use below query.
BasicDBObject.parse("
{'$eq':[
{'$subtract':[
{'$ifNull':['$acceptedDate',{'$date':" + System.currentTimeMillis() + "}]},
{'$subtract':['$lastVisit', 216000000]} // 5 days
]},
432000000 // 10 days
]}"
);

Related

Binary search on a sorted list of E(startTime,endTime) to find all E's matched by a given time range (t1,t2)

I have Event objects as follows,
public class Event {
String name;
int startTime; // minutes since midnight, e.g 4:15am = 255
int endTime; // minutes since midnight, e.g.6:30am = 390
// + getters/setters
}
They are sorted by startTime ASC,
events.sort(Comparator.comparing(Event::getStartTime));
Events can overlap in any way.
I need to obtain a List of all Events matching (incl. partially) a particular range t1,t2 (also ints for minutes since midnight).
List<Event> eventsMatching = findMatching(t1, t2); // e.g. between 200,205
I don't want to go through the whole list and check e.getStartTime() <= t1 && e.getEndTime() >= t2. Since the list is sorted, I should be able to use Collections.binarySearch() in some way. But normally, a binary search finds the exact object you're looking for: int position = Collections.binarySearch(events, key). Is there some way to find matching ranges quickly using a binary search?
You need to check for all events that meet e.startTime <= t1.
record Event(String name, int startTime, int endTime) {}
List<Event> list = Arrays.asList(
new Event("a", 2, 3), new Event("b", 3, 4),
new Event("c", 0, 1), new Event("d", 4, 5));
list.sort(Comparator.comparing(Event::startTime));
System.out.println("sorted: " + list);
int t1 = 2, t2 = 3;
List<Event> filtered = list.stream()
.takeWhile(e -> e.startTime() <= t1)
.peek(e -> System.out.println("checked: " + e))
.filter(e -> e.endTime() >= t2)
.toList();
System.out.println("filtered: " + filtered);
output:
sorted: [Event[name=c, startTime=0, endTime=1], Event[name=a, startTime=2, endTime=3], Event[name=b, startTime=3, endTime=4], Event[name=d, startTime=4, endTime=5]]
checked: Event[name=c, startTime=0, endTime=1]
checked: Event[name=a, startTime=2, endTime=3]
filtered: [Event[name=a, startTime=2, endTime=3]]
Binary search will not help you much, because you're not searching for a single equality-based match, but rather a range of results that can be ordered, but not in a way that's much help to quickly finding matches.
Unless you're dealing with a great many range elements (1000's), a linear (ie O(n)) process would work OK.
To speed things up, sort by start date beforehand, so your iteration over the list would be able to exit early when you encounter an element whose start date is after the target.
You should browse the list and stop when an item is not in range. In terms of complexity, it's the best you can do.

Given a list of times, how to group them in such a way that close times are in the same group and distant ones are not? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
Suppose I have a list of timestamps with the same date of the type ZonedDateTime.
Instead of printing them out, I'd like to be able to group them in some say, and just print intervals e.g
07:41:05 - 07:55:46
08:21:35 - 08:45:42 //first being the first elem of the group, second being the last
etc
I was just thinking about converting them all to milliseconds first, then sorting the timestamps, and maybe choose a value like 100000 milliseconds as a separator, so if two timestamp millisecond values are less than 100000 milliseconds apart, i consider them as part of the same group.
In worst case all timestamps are in this distance apart when sorted, and then I have a huge group where the start and end elements of the interval are hours apart, but i hope that's not likely to happen with the given dataset.
Is there a better way to do this?
The question is not answered yet.
Using k-means:
// sample data
List<ZonedDateTime> xs = IntStream.range(0, 10).mapToObj(n ->
ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
.plus(ThreadLocalRandom.current().nextInt(0, 24 * 60), ChronoUnit.MINUTES))
.collect(toList());
// assume xs is not empty
ZonedDateTime day = xs.get(0).truncatedTo(ChronoUnit.DAYS);
final int WINDOWS = 3;
System.out.printf("== fixed windows (millis precision) using k-means%n");
Map<Double, List<ZonedDateTime>> points = xs.stream()
.collect(groupingBy(x -> (double) ((x.toInstant().toEpochMilli() - day.toInstant().toEpochMilli()) / 1000), toList()));
Double[] keys = points.keySet().stream().sorted().toArray(Double[]::new);
double[][] kpoints = new double[keys.length][2];
// put keys along f(x)=0 line
for (int i = 0; i < keys.length; i++) {
kpoints[i][0] = keys[i];
kpoints[i][1] = 0;
}
double[][] centroids = new double[WINDOWS][2];
for (int i = 0; i < WINDOWS; i++) {
centroids[i][0] = ThreadLocalRandom.current().nextDouble(keys[0], keys[keys.length - 1]);
centroids[i][1] = 0;
}
final EKmeans eKmeans = new EKmeans(centroids, kpoints);
eKmeans.run();
// regroup
int[] igroup = eKmeans.getAssignments();
Map<Integer, List<ZonedDateTime>> groups =
IntStream.range(0, igroup.length).boxed()
.collect(groupingBy(i -> igroup[i], collectingAndThen(toList(),
rs -> rs.stream().flatMap(r -> points.get(keys[r]).stream()).collect(toList()))));
groups.forEach((k, rs) -> {
System.out.printf(" - group %d%n", k);
rs.forEach(r -> System.out.printf(" %s%n", r.format(ISO_LOCAL_TIME)));
});
with output
== fixed windows (millis precision) using k-means
- group 0
03:09:00
03:22:00
05:22:00
05:38:00
07:34:00
- group 1
16:30:00
18:25:00
- group 2
11:23:00
11:48:00
14:07:00

Grouping LocalDateTime objects in intervals using Java 8

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;
}
}

sum values in LinkedList Based on Grouping by specific year in key using Java

I have a linked list like below.
public static void main(String[] args) {
LinkedHashMap<String, Integer> month = new LinkedHashMap<>();
TreeMap<String, Integer> output = new TreeMap<>();
month.put("May2021", 1);
month.put("May2022", 1);
month.put("Oct2022", 2);
month.put("Oct2020", 1);
month.put("Oct2021", 2);
month.put("Jan2021", 1);
month.put("Jan2022", 4);
month.put("Aug2021", 1);
month.put("Aug2022", 3);
month.put("Aug2020", 4);
month.put("Nov2021", 8);
month.put("Dec2021", 1);
month.put("Dec2020", 2);
month.put("Nov2020", 5);
month.put("Mar2021", 1);
month.put("Mar2022", 1);
month.put("Jul2022", 1);
month.put("Jul2021", 2);
month.put("Feb2021", 1);
month.put("Feb2022", 1);
month.put("Apr2022", 1);
month.put("Apr2021", 1);
month.put("Jun2022", 1);
month.put("Jun2021", 1);
month.put("Sep2020", 6);
month.put("Sep2021", 1);
month.put("Sep2022", 1);
I need the output as a treemap as shown below - provided the below conditions are met,
// 2020 Should have sum of fiscal years starting Feb2020 to Jan2021
// 2021 Should have sum of fiscal years starting Feb2021 to Jan2022
// 2022 Should have sum of fiscal years starting Feb2022 to Jan2023
/*
{ "2020" :18 },
{ "2021" :25 },
{ "2022" :35 }
It should match all the conditions above.
I have tried like this, but i am finding difficulties to achieve the result. Any help would be appreciated.
month.entrySet().forEach(monthYear -> {
String[] splitMonthYear = monthYear.getKey().split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
String fullMonth = splitMonthYear[0];
String fullYear = splitMonthYear[1];
if (fullMonth.equalsIgnoreCase("jan")) {
output.put(String.valueOf(Integer.parseInt(fullYear) - 1), monthYear);
} else {
output.put(fullYear, monthYear);
}
});
A simple solution using for loop.
TreeMap<Integer, Integer> output = new TreeMap<>();
for(String key : month.keySet()){
String mon = key.substring(0,3);
int val = Integer.parseInt(key.substring(3));
if(mon.equals("Jan")){
output.put(val-1, output.getOrDefault(val-1, 0)+month.get(key));
}else{
output.put(val, output.getOrDefault(val, 0)+month.get(key));
}
}
As others mentioned, output would be
{2020=19, 2021=24, 2022=12}
According to this logic:
// 2020 Should have sum of fiscal years starting Feb2020 to Jan2021
// 2021 Should have sum of fiscal years starting Feb2021 to Jan2022
// 2022 Should have sum of fiscal years starting Feb2022 to Jan2023
This shouldnt be the answer:
{ "2020" :18 },
{ "2021" :25 },
{ "2022" :35 }
Rather it should be(for the given input):
{ "2020" :19 },
{ "2021" :24 },
{ "2022" :12 }
which can be achieved using this simple technique: (instead of using complicated regex and all)
month.forEach((key,val) -> {
String year = "";
if(key.substring(0,3).equalsIgnoreCase("jan")){
year = Integer.toString(Integer.parseInt(key.substring(3)) -1);
}else{
year = key.substring(3);
}
if(output.containsKey(year)){
output.put(year,output.get(year) + val);
}else{
output.put(year,val);
}
});
Algorithm is ::
Check if the month is Jan. if so , the year should be 1 less (coz that's what the question says).
Parse the year (which is just the last 4 characters of the strings) and subtract that 1 (if required) and then put in your Treemap.
If entry already exists in treemap, simply add it to the existing value to get cumulative sum.
You can use Collectors.groupingBy to group the stream elements by a key. The key is determined by the getYearToGroupUnder method whose logic is same as yours (Note: The regex might be simplified).
By using Collectors.summingInt as a downstream collector, we sum up the values of all map entries that gets mapped to the same key.
output = month.entrySet()
.stream()
.collect(Collectors.groupingBy(e -> getYearToGroupUnder(e.getKey()),
TreeMap::new,
Collectors.summingInt(Map.Entry::getValue)));
private static String getYearToGroupUnder(String monthYear) {
String[] splitMonthYear = monthYear.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
String fullMonth = splitMonthYear[0];
String fullYear = splitMonthYear[1];
return fullMonth.equalsIgnoreCase("jan")
? String.valueOf(Integer.parseInt(fullYear) - 1)
: fullYear;
}
This is the result (seems your expected result is wrong for the data you have posted)
{2020=19, 2021=24, 2022=12}

With Java 8 streams, How to find items with specific conditions within a specific period

I need help to find out how to find something in java which is currently being done in sql. I need to find specific data in a list within a specific time duration using streams
scenario:
I have a Bet Object with String CustId, Instant timestamp, Double betAmount
I need to find all customers which passed the 100.00 limit per 24 hour period, how would i do it in Java 8?
The method would be
public List<String> findLimits(List<Bet> bets){
...
}
sample data :
note: to be parsed in List<Bet>
A00001 2019-01-15T02:01:10 43.00
A00001 2019-01-15T04:00:00 13.00
A00001 2019-01-15T04:01:15 50.00
B00034 2019-01-15T05:00:00 15.00
A00001 2019-01-15T06:56:20 5.00
B00034 2019-01-15T06:57:00 20.00
C00004 2019-01-15T07:01:00 90.00
C00004 2019-01-15T07:11:00 30.00
B00034 2019-01-17T01:00:00 90.00
expected result:
["A00001","C00004"] (List<String>)
note: the list would contain all bets with diff cust id and chronological timestamps
The sliding 24 hour period and grouping the customers combined is a tricky one that i'm trying to solve.
You can map dates with the sum of bet amounts. Then filter them.
public List<String> findLimits(List<Bet> bets) {
return bets.stream()
.collect(Collectors.toMap(
b -> b.getCustId() + LocalDate.ofInstant(b.getTimestamp(), ZoneOffset.UTC).toString(),
Bet::getAmount,
(o1, o2) -> o1 + o2))
.entrySet().stream()
.filter(e -> e.getValue() > 100.0)
.map(e -> e.getKey().substring(0, e.getKey().length() - LocalDate.EPOCH.toString().length()))
.collect(Collectors.toList());
}
Firstly you can group data by customer ID and then analyze sums in 24h periods. As you mentioned records are sorted by date ascending, so findLimits method can look like below:
class Bet {
String ID;
LocalDateTime dateTime;
BigDecimal value;
}
public List<String> findLimits(List<Bet> bets) {
BigDecimal sumLimit = new BigDecimal(100);
Map<String, List<Bet>> map = new HashMap<String, List<Bet>>();
List<String> result = new ArrayList<String>();
for (Bet bet : bets) {
if (map.get(bet.ID) == null)
map.put(bet.ID, new ArrayList<Bet>());
map.get(bet.ID).add(bet);
}
for (String ID : map.keySet()) {
List<Bet> betListForCustomer = map.get(ID);
boolean customerExceededLimit = false;
for (int i = 0; i < betListForCustomer.size(); i++) {
LocalDateTime endOfPeriod = betListForCustomer.get(i).dateTime.plusDays(1); //calculating end of 24h period current data
BigDecimal sum = new BigDecimal(0);
for (int j = i; j < betListForCustomer.size() //move start period to next dateTime
&& endOfPeriod.isAfter(betListForCustomer.get(j).dateTime); j++) { //analyzing to the last date in 24h period or end data set
sum = sum.add(betListForCustomer.get(j).value);
}
if (sum.compareTo(sumLimit) >= 0) { //sum >= 100
customerExceededLimit = true;
break; //there is no need to analyze this customer, limit exceeded
}
}
if (customerExceededLimit) {
result.add(ID);
}
}
return result;
}

Categories