More than 2 groupingBy operations in Java 8 - java

This is my "revenue_data.csv" file:
Client ReportDate Revenue
C1 2019-1-7 12
C2 2019-1-7 34
C1 2019-1-16 56
C2 2019-1-16 78
C3 2019-1-16 90
And my case class to read the file is:
package com.source.code;
import java.time.LocalDate;
public class RevenueRecorder {
private String clientCode;
private LocalDate reportDate;
private int revenue;
public RevenueRecorder(String clientCode, LocalDate reportDate, int revenue) {
this.clientCode = clientCode;
this.reportDate = reportDate;
this.revenue = revenue;
}
public String getClientCode() {
return clientCode;
}
public LocalDate getReportDate() {
return reportDate;
}
public int getRevenue() {
return revenue;
}
}
I can read the file and group by ReportDate, sum(revenue) in the following manner:
import com.source.code.RevenueRecorder;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.summingInt;
public class RevenueRecorderMain {
public static void main(String[] args) throws IOException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-d");
List<RevenueRecorder> revenueRecords = new ArrayList<>();
Path path = FileSystems.getDefault().getPath("src", "main", "resources",
"data", "revenue_data.csv");
Files.lines(path)
.skip(1)
.map(s -> s.split(","))
.forEach(s ->
{
String clientCode = s[0];
LocalDate reportDate = LocalDate.parse(s[1], formatter);
int revenue = Integer.parseInt(s[2]);
revenueRecords.add(new RevenueRecorder(clientCode, reportDate, revenue));
});
Map<LocalDate, Integer> reportDateRev = revenueRecords.stream()
.collect(groupingBy(RevenueRecorder::getReportDate,
summingInt(RevenueRecorder::getRevenue)));
}
}
My question is how can I group by ReportDate, count(clientCode) and sum(revenue) in Java 8, specifically:
what collection to use instead of the Map
how to groupby and collect in this case (and generally for more than 2 groupingBy's)
I'm trying:
//import org.apache.commons.lang3.tuple.ImmutablePair;
//import org.apache.commons.lang3.tuple.Pair;
Map<LocalDate, Pair<Integer, Integer>> pairedReportDateRev = revenueRecords.stream()
.collect(groupingBy(RevenueRecorder::getReportDate,
new ImmutablePair(summingInt(RevenueRecorder::getRevenue),
groupingBy(RevenueRecorder::getClientCode, Collectors.counting()))));
But getting the Intellij red-squiggle underneath RevenueRecorder::getReportDate with the hover-message 'Non-static method cannot be referenced from a static context'.
Thanks
EDIT
For clarification, here's the corresponding SQL query that I'm trying to get at:
select
reportDate, count(distinct(clientCode)), sum(revenue)
from
revenue_data_table
group by
reportDate

Although your trying has not been successful, but I think is what you most want to express. So I just follow your code and fix it. Try this one!
Map<LocalDate, ImmutablePair<Integer, Map<String, Long>>> map = revenueRecords.stream()
.collect(groupingBy(RevenueRecorder::getReportDate,
collectingAndThen(toList(), list -> new ImmutablePair(list.stream().collect(summingInt(RevenueRecorder::getRevenue)),
list.stream().collect(groupingBy(RevenueRecorder::getClientCode, Collectors.counting()))))));
And I borrowed some sample data code from #Lyashko Kirill to test my code, the result is below
This's my own idea, I hope I can help you. ╰( ̄▽ ̄)╭

If you already use Java 12, there is a new collector Collectors.teeing() which allows to collect using two independent collectors, then merge their results using the supplied BiFunction. Every element passed to the resulting collector is processed by both downstream collectors, then their results are merged using the specified merge function into the final result. Therefor Collectors.teeing() may be a good fit since you want counting and summing.
Map<LocalDate, Result> pairedReportDateMRR =
revenueRecords.stream().collect(Collectors.groupingBy(RevenueRecorder::getReportDate,
Collectors.teeing(Collectors.counting(),
Collectors.summingInt(RevenueRecorder::getRevenue), Result::new)));
System.out.println(pairedReportDateMRR);
//output: {2019-01-07={count=2, sum=46}, 2019-01-16={count=3, sum=224}}
For testing purposes I used the following simple static class
static class Result {
private Long count;
private Integer sum;
public Result(Long count, Integer sum) {
this.count = count;
this.sum = sum;
}
#Override
public String toString() {
return "{" + "count=" + count + ", sum=" + sum + '}';
}
}

First of all you can't produce map Map<LocalDate, Pair<Integer, Integer>> due to you want to do the second grouping, what means that for the same date you may have multiple Client Codes with separate counters per each of them.
So if I've got you right you wont to get something like this Map<LocalDate, MutablePair<Integer, Map<String, Integer>>>, if it's correct try this code snippet:
public static void main(String[] args) {
String data = "C1,2019-1-7,12\n" +
"C2,2019-1-7,34\n" +
"C1,2019-1-16,56\n" +
"C2,2019-1-16,78\n" +
"C3,2019-1-16,90";
Stream.of(data.split("\n")).forEach(System.out::println);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-d");
List<RevenueRecorder> revenueRecords = Stream.of(data.split("\n")).map(line -> {
String[] s = line.split(",");
String clientCode = s[0];
LocalDate reportDate = LocalDate.parse(s[1].trim(), formatter);
int revenue = Integer.parseInt(s[2]);
return new RevenueRecorder(clientCode, reportDate, revenue);
}).collect(toList());
Supplier<MutablePair<Integer, Map<String, Integer>>> supplier = () -> MutablePair.of(0, new HashMap<>());
BiConsumer<MutablePair<Integer, Map<String, Integer>>, RevenueRecorder> accumulator = (pair, recorder) -> {
pair.setLeft(pair.getLeft() + recorder.getRevenue());
pair.getRight().merge(recorder.getClientCode(), 1, Integer::sum);
};
BinaryOperator<MutablePair<Integer, Map<String, Integer>>> combiner = (p1, p2) -> {
p1.setLeft(p1.getLeft() + p2.getLeft());
p2.getRight().forEach((key, val) -> p1.getRight().merge(key, val, Integer::sum));
return p1;
};
Map<LocalDate, MutablePair<Integer, Map<String, Integer>>> pairedReportDateMRR = revenueRecords.stream()
.collect(
groupingBy(RevenueRecorder::getReportDate,
Collector.of(supplier, accumulator, combiner))
);
System.out.println(pairedReportDateMRR);
}

Related

How to sort Strings based on string date and dynamic substring in java?

I am looking to sort the strings based on
Date(yyMMdd) present in string.
Dynamic substring of the given string.(att, dscl, xml)
These are the input strings.
f1660.msg.220523.xml.gpg
f1660.msg.dscl.220523.xml.gpg
f1660.msg.att.220523.tar.gz.gpg
f1660.cit.msg.220520.xml1.gpg
f1660.cit.msg.220520.xml2.gpg
f1660.cit.msg.att.220520.tar.gz.gpg
f1660.cit.msg.dscl.220520.xml.gpg
8701D6.msg.220524.xml.gpg
8701D6.dscl.220524.xml.gpg
8701D6.msg.att.220524.tar.gz.gpg
Expected result: Sorted based on date (220520, 220523, 220524) and all the same date files should be sorted in the order where .att file first, .dscl file second and rest all .xml files 3rd position on wards.
f1660.cit.msg.att.220520.tar.gz.gpg
f1660.cit.msg.dscl.220520.xml.gpg
f1660.cit.msg.220520.xml1.gpg
f1660.cit.msg.220520.xml2.gpg
f1660.msg.att.220523.tar.gz.gpg
f1660.msg.dscl.220523.xml.gpg
f1660.msg.220523.xml.gpg
8701D6.msg.att.220524.tar.gz.gpg
8701D6.dscl.220524.xml.gpg
8701D6.msg.220524.xml.gpg
Tried extracting date and sorting based on date but i am not getting how to achieve it when date and dynamic substring is part of strings which i want to sort.
Could you please help?
Code tried.
public void sortBasedOnDate() {
ArrayList<String> names = new ArrayList<>();
names.add("f1660.msg.220523.xml.gpg");
names.add("f1660.msg.dscl.220523.xml.gpg");
names.add("f1660.msg.att.220523.tar.gz.gpg");
names.add("f1660.cit.msg.220520.xml1.gpg");
names.add("f1660.cit.msg.220520.xml2.gpg");
names.add("f1660.cit.msg.att.220520.tar.gz.gpg");
names.add("f1660.cit.msg.dscl.220520.xml.gpg");
names.add("8701D6.msg.220524.xml.gpg");
names.add("8701D6.dscl.220524.xml.gpg");
names.add("8701D6.msg.att.220524.tar.gz.gpg");
TreeMap<LocalDate, List<String>> map = new TreeMap<>(new SortByDate());
for (String name : names) {
LocalDate localDate = getDateFromFileName(name);
List<String> mapList = map.get(localDate);
if (mapList == null) {
mapList = new ArrayList<>();
mapList.add(name);
map.put(localDate, mapList);
} else {
mapList.add(name);
}
}
List<String> sortedList = new LinkedList<>();
for (Map.Entry<LocalDate, List<String>> entry : map.entrySet()) {
sortedList.addAll(entry.getValue());
}
System.out.println("sortedList: "+sortedList);
}
private LocalDate getDateFromFileName(String fileName) {
String fileUtilDateStr = "";
Optional<String> streamResult = Arrays.stream(fileName.split("\\."))
.filter(element -> Pattern.compile("(\\d){6}").matcher(element).matches()).findFirst();
if (streamResult.isPresent()) {
fileUtilDateStr = streamResult.get();
}
DateTimeFormatter formatters = DateTimeFormatter.ofPattern("yyMMdd");
LocalDate parsedDate = LocalDate.parse(fileUtilDateStr, formatters);
return parsedDate;
}
public class SortByDate implements Comparator<LocalDate> {
#Override
public int compare(LocalDate date1, LocalDate date2) {
return date1.compareTo(date2);
}
}
output:
sortedList: [f1660.cit.msg.220520.xml1.gpg,
f1660.cit.msg.220520.xml2.gpg, f1660.cit.msg.att.220520.tar.gz.gpg, f1660.cit.msg.dscl.220520.xml.gpg, f1660.msg.220523.xml.gpg, f1660.msg.dscl.220523.xml.gpg, f1660.msg.att.220523.tar.gz.gpg, 8701D6.msg.220524.xml.gpg, 8701D6.dscl.220524.xml.gpg, 8701D6.msg.att.220524.tar.gz.gpg]
But the expected list:
sortedList: [f1660.cit.msg.att.220520.tar.gz.gpg,
f1660.cit.msg.dscl.220520.xml.gpg, f1660.cit.msg.220520.xml1.gpg, f1660.cit.msg.220520.xml2.gpg, f1660.msg.att.220523.tar.gz.gpg, f1660.msg.dscl.220523.xml.gpg, f1660.msg.220523.xml.gpg, 8701D6.msg.att.220524.tar.gz.gpg, 8701D6.dscl.220524.xml.gpg, 8701D6.msg.220524.xml.gpg]
It is sorting based on date but not getting how to sort them with 2nd condition i.e in the order of att, dscl and xml for within same date.
You can
use a regex to get the comparison relevant part of the string, i.e.
dscl.220523 att.220523 msg.220523 ...
extract date and extension from it using a function, method or UnaryOperator
create a priority map for your extensions
create a comparator which compares the date part by parsing it to LocalDate
create a comparator which compares the extensions by the value of the priority map
and finally chain the comparators to sort your list as desired
Example:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Map;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Example {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("f1660.msg.220523.xml.gpg");
names.add("f1660.msg.dscl.220523.xml.gpg");
names.add("f1660.msg.att.220523.tar.gz.gpg");
names.add("f1660.cit.msg.220520.xml1.gpg");
names.add("f1660.cit.msg.220520.xml2.gpg");
names.add("f1660.cit.msg.att.220520.tar.gz.gpg");
names.add("f1660.cit.msg.dscl.220520.xml.gpg");
names.add("8701D6.msg.220524.xml.gpg");
names.add("8701D6.dscl.220524.xml.gpg");
names.add("8701D6.msg.att.220524.tar.gz.gpg");
Pattern pattern = Pattern.compile("\\.(\\w+)\\.(\\d+)\\.");
UnaryOperator<String> extnExtractor = s -> {
Matcher m = pattern.matcher(s);
m.find();
return m.group(1);
};
UnaryOperator<String> dateExtractor = s -> {
Matcher m = pattern.matcher(s);
m.find();
return m.group(2);
};
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyMMdd");
Map<String,Integer> prio = new HashMap<>();
prio.put("att", 1);
prio.put("dscl", 2);
Comparator<String> byDate = Comparator.comparing(s -> LocalDate.parse(dateExtractor.apply(s), dtf));
Comparator<String> byExt = Comparator.comparing(s -> prio.getOrDefault(extnExtractor.apply(s),3));
names.sort(byDate.thenComparing(byExt));
names.forEach(System.out::println);
}
}
The problem was that you use a TreeMap<LocalDate, List<String>>. This means that every LocalDate is mapped to a List<String>, so you basically have multiple Lists.
getDateFromFileName(String) works.
You want your Comparator to be a Comparator<String> to actually sort a List<String> and not something else:
public static class SortByDate implements Comparator<String> {
private final HashMap<String, LocalDate> map;
public SortByDate(ArrayList<String> names) {
this.map = new HashMap<>();
for (String name : names) map.put(name, getDateFromFileName(name));
}
#Override
public int compare(String o1, String o2) {
return map.get(o1).compareTo(map.get(o2));
}
}
Now create a sorted list like this:
List<String> sortedList = new ArrayList<>(names);
sortedList.sort(new SortByDate(names));
List<String> names = new ArrayList<>();
names.add("f1660.msg.220523.xml.gpg");
names.add("f1660.msg.dscl.220523.xml.gpg");
names.add("f1660.msg.att.220523.tar.gz.gpg");
names.add("f1660.cit.msg.220520.xml1.gpg");
names.add("f1660.cit.msg.220520.xml2.gpg");
names.add("f1660.cit.msg.att.220520.tar.gz.gpg");
names.add("f1660.cit.msg.dscl.220520.xml.gpg");
names.add("8701D6.msg.220524.xml.gpg");
names.add("8701D6.dscl.220524.xml.gpg");
names.add("8701D6.msg.att.220524.tar.gz.gpg");
Comparator<String> comparator = new Comparator<String>() {
Pattern pattern = Pattern.compile("\\.\\d{6}\\.");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyMMdd");
#Override
public int compare(String o1, String o2) {
LocalDate dateA = getDate(o1);
LocalDate dateB = getDate(o2);
return dateA.compareTo(dateB);
}
private LocalDate getDate(String val) {
Matcher matcher = pattern.matcher(val);
if (matcher.find()) {
String strVal = matcher.group();
strVal = strVal.substring(1, strVal.length()-1);
return LocalDate.parse(strVal, formatter);
}
return LocalDate.now();
}
};
names.sort(comparator);
names.forEach(System.out::println);
This will work
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class Demo {
public static void main(String[] args) {
ArrayList<String> strings = new ArrayList<>();
strings.add("f1660.msg.220523.xml.gpg");
strings.add("f1660.msg.dscl.220523.xml.gpg");
strings.add("f1660.msg.att.220523.tar.gz.gpg");
strings.add("f1660.cit.msg.220520.xml1.gpg");
strings.add("f1660.cit.msg.att.220520.tar.gz.gpg");
strings.add("f1660.cit.msg.dscl.220520.xml.gpg");
strings.add("8701D6.msg.220524.xml.gpg");
strings.add("8701D6.dscl.220524.xml.gpg");
strings.add("8701D6.msg.att.220524.tar.gz.gpg");
HashMap<Integer, Integer> indexDateMap = new HashMap<>();
for (int i = 0; i < strings.size(); i++) {
Pattern pattern = Pattern.compile("\\d{6}");
int finalI = i;
pattern.matcher(strings.get(i)).results() // Stream<MatchResult>
.map(mr -> mr.group(0)).forEach(str -> {
indexDateMap.put(finalI, Integer.parseInt(str));
});// Stream<String> - the 1st group of each result
}
indexDateMap.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach(index -> {
System.out.println(strings.get(index.getKey()));
});
}
}
OUTPUT:
f1660.cit.msg.220520.xml1.gpg
f1660.cit.msg.att.220520.tar.gz.gpg
f1660.cit.msg.dscl.220520.xml.gpg
f1660.msg.220523.xml.gpg
f1660.msg.dscl.220523.xml.gpg
f1660.msg.att.220523.tar.gz.gpg
8701D6.msg.220524.xml.gpg
8701D6.dscl.220524.xml.gpg
8701D6.msg.att.220524.tar.gz.gpg
Using Ryan's code created the date comparator and I have added an extra comparator for extension sorting.
1st Comparator
public class DateComparator implements Comparator<String> {
#Override
public int compare(String o1, String o2) {
LocalDate date1 = getDate(o1);
LocalDate date2 = getDate(o2);
return date1.compareTo(date2);
}
private LocalDate getDate(String val) {
Pattern pattern = Pattern.compile("\\.\\d{6}\\.");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyMMdd");
Matcher matcher = pattern.matcher(val);
if (matcher.find()) {
String sDate = matcher.group();
sDate = sDate.substring(1, sDate.length()-1);
return LocalDate.parse(sDate, formatter);
}
return LocalDate.now();
}
}
2nd Comparator
public class ExtComparator implements Comparator<String > {
#Override
public int compare(String o1, String o2) {
Pattern pattern = Pattern.compile("\\.(\\w+)\\.(\\d{6})\\.");
Matcher matcher = pattern.matcher(o1);
String s1 = matcher.find()?matcher.group():"";
matcher = pattern.matcher(o2);
String s2 = matcher.find()?matcher.group():"";
return s1.compareTo(s2);
}
}
Calling these 2 comparators in order will do the job.
list.sort(new DateComparator().thenComparing(new ExtComparator()));
list contains all the input strings
Some thoughts around the performance of possible solutions:
Don't bother converting the date string into a Date object. The yymmdd format is already sortable as a string, so converting it to another object is wasteful.
Be aware that a Comparator will be called many times with the same object (because it compares it with many other objects). If it takes time to extract the date from one object, do it just once. You can put the date into a map.
You can simply create a priority code ("att" being first, "dscl" second, others third) and add it to the end of the date string and then sort by that combination.
Here's a full runnable example, together with unit test:
package org.example;
import org.junit.jupiter.api.Test;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.assertj.core.api.Assertions.assertThat;
class Example {
private static final Pattern FILENAME_PATTERN = Pattern.compile("\\.(att\\.|dscl\\.|)(\\d{6})\\.");
private static final List<String> priority = List.of("att.", "dscl.", "");
List<String> sortedFilenames(List<String> filenames) {
Map<String, String> sortCodes = new HashMap<>();
for (String filename : filenames) {
Matcher matcher = FILENAME_PATTERN.matcher(filename);
if (matcher.find()) {
String type = matcher.group(1);
String date = matcher.group(2);
sortCodes.put(filename, date + priority.indexOf(type));
} else {
sortCodes.put(filename, "999999z");
}
}
return filenames.stream().sorted(Comparator.comparing(sortCodes::get)).toList();
}
#Test
void test() {
List<String> filenames = """
f1660.msg.220523.xml.gpg
f1660.msg.dscl.220523.xml.gpg
f1660.msg.att.220523.tar.gz.gpg
f1660.cit.msg.220520.xml1.gpg
f1660.cit.msg.220520.xml2.gpg
f1660.cit.msg.att.220520.tar.gz.gpg
f1660.cit.msg.dscl.220520.xml.gpg
8701D6.msg.220524.xml.gpg
8701D6.dscl.220524.xml.gpg
8701D6.msg.att.220524.tar.gz.gpg
""".lines().toList();
List<String> sortedFilenames = sortedFilenames(filenames);
List<String> expected = """
f1660.cit.msg.att.220520.tar.gz.gpg
f1660.cit.msg.dscl.220520.xml.gpg
f1660.cit.msg.220520.xml1.gpg
f1660.cit.msg.220520.xml2.gpg
f1660.msg.att.220523.tar.gz.gpg
f1660.msg.dscl.220523.xml.gpg
f1660.msg.220523.xml.gpg
8701D6.msg.att.220524.tar.gz.gpg
8701D6.dscl.220524.xml.gpg
8701D6.msg.220524.xml.gpg
""".lines().toList();
assertThat(sortedFilenames).isEqualTo(expected);
}
}

Store the data from text file to a hashmap

I am trying to read a text file and store with a hashmap. The file contains information like this:
1946-01-12;13:00:00;0.3;G
1946-01-12;18:00:00;-2.8;G
1946-01-13;07:00:00;-6.2;G
1946-01-13;13:00:00;-4.7;G
1946-01-13;18:00:00;-4.3;G
1946-01-14;07:00:00;-1.5;G
1946-01-14;13:00:00;-0.2;G
I want to store the dates as keys and then "13:00:00;0.3;G" as value, where 13:00 is time, 0.3 is temperature and G represent a quality code. I wonder if this is even possbile since many rows in the file has the same date? I already wrote a code for storing the data in a list, but now I want to store it in a map instead. My old code looks like this:
/**
* Provides methods to retrieve temperature data from a weather station file.
*/
public class WeatherDataHandler {
private List<Weather> weatherData = new ArrayList<>();
public void loadData(String filePath) throws IOException {
List<String> fileData = Files.readAllLines(Paths.get("filepath"));
for(String str : fileData) {
List<String> parsed = parseData(str);
LocalDate date = LocalDate.parse(parsed.get(0));
LocalTime time = LocalTime.parse(parsed.get(1));
double temperature = Double.parseDouble(parsed.get(2));
String quality = parsed.get(3);
//new Weather object
Weather weather = new Weather(date, time, temperature, quality);
weatherData.add(weather);
}
}
private List<String> parseData(String s) {
return Arrays.asList(s.split(";"));
}
I got stuck when implementing the hashmap. I started with some code below, but I do not know how to loop over a sequence of dates. What is the simplest way to store the data from the file in a map?
public class WeatherDataHandler {
public void loadData(String filePath) throws IOException {
Map<LocalDate, String> map =new HashMap<LocalDate, String>();
BufferedReader br = new BufferedReader(new FileReader("filepath"));
String line="";
int i=0;
while (line != null) {
line = br.readLine();
map.put(i,line);
i++;
}
String date="";
String time="";
String temperature="";
String quality="";
for(int j=0;j<map.size();j++){
if(!(map.get(j)== null)){
String[] getData=map.get(j).toString().split("\\,");
date = getData[0];
time = getData[1];
temperature = getData[2];
quality = getData[3];
}
}
}
Using the stream API you can create a map where the key is the date and the [map] value is a list.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class WeatherDataHandler {
public static void main(String[] args) {
Path path = Paths.get("filepath");
try {
Map<String, List<String>> map = Files.lines(path)
.collect(Collectors.groupingBy(line -> line.split(";", 2)[0]));
map.entrySet()
.stream()
.forEach(entry -> System.out.printf("%s = %s%n", entry.getKey(), entry.getValue()));
}
catch (IOException x) {
x.printStackTrace();
}
}
}
Method lines() in class java.nio.file.Files creates a stream where each stream element is a single line of the file being read.
Method split() splits the line into a two element array (because of the second argument which is the number 2).
The first array element, i.e. the date, becomes the [map] key and the rest of the line becomes the [map] value.
Whenever a duplicate key is encountered, the value is appended to the existing value creating a list. Hence the type parameters for the map are String for the [map] key and List<String> for the [map] value.
Running the above code on your sample data yields:
1946-01-14 = [1946-01-14;07:00:00;-1.5;G, 1946-01-14;13:00:00;-0.2;G]
1946-01-12 = [1946-01-12;13:00:00;0.3;G , 1946-01-12;18:00:00;-2.8;G]
1946-01-13 = [1946-01-13;07:00:00;-6.2;G , 1946-01-13;13:00:00;-4.7;G, 1946-01-13;18:00:00;-4.3;G ]

Problems with List<Map<String, Object>> streaming

I have a List of Map.
This list contains maps that have two fields: date and username (both are string)
For example:
data index 0: [0] "datetime" -> "2020-01-31 10:45:33"
[1] "username" -> "myself"
data index 1: [0] "datetime" -> "2020-01-31 10:45:48"
[1] "username" -> "myself"
data index 2: [0] "datetime" -> "2020-01-30 02:45:33"
[1] "username" -> "myself2"
data index 3: [0] "datetime" -> "2020-03-15 10:33:48"
[1] "username" -> "myself2"
(The datetime format is yyyy-MM-dd hh:mm:ss)
My goal is to retrieve a list of distinct username with the datetime associated and where the datetime is between now (LocalDateTime.now()) and 5mn before.
So if it's 11:41am, I want to retrieve a list of distinct user + their most recent datetime where this datetime is > 11:36am.
In the example provided, I'll retrieve :
"myself" -> "2020-01-31 10:45:48"
"myself2" -> "2020-03-15 10:33:48"
I don't know how to filter this list of maps by parsing the string into datetime. Any ideas or advices ? Thanks a lot.
This is just to give you an idea how this could be done. There are a few steps involved in this process:
transform each map to "myself" -> "2020-01-31 10:45:33"
obtain a stream of all intermediate maps
group by Map.Entry::getKey
select maximum time
var pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
var list = List.of(
Map.of("datetime", "2020-01-31 10:45:33", "username" , "myself"),
Map.of("datetime","2020-01-31 10:45:48", "username", "myself")
);
now let's transform the list to Map<String, Optional<LocalDateTime> where
key is user, and value is maximum time for that user:
list.stream()
// creating an intermediate map of user -> time
.map(map -> Map.of(map.get("username"), LocalDateTime.parse(map.get("datetime"), pattern)))
// steam containing temporary maps
.flatMap(map -> map.entrySet().stream())
.collect(
// group by each user name
groupingBy(Map.Entry::getKey,
// selecting the maximum/latest time
maxBy(Comparator.comparing(Map.Entry<String, LocalDateTime>::getValue))
)
);
Please note that this is just a suggestion to give you an idea. The code has to be adjusted and possible there is a more elegant solution for this. I hope this helps.
Map<String, String> myMap = Map.of(
"2020-01-31 10:45:33", "myself",
"2020-01-31 10:45:48", "myself",
"2020-01-30 02:45:33", "myself2",
"2020-03-15 10:33:48", "myself2"
);
String now = LocalDateTime
.of(2020,3,1,11,41)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
myMap.keySet().stream()
.filter(key -> key.compareTo(now) < 0)
.forEach(key -> System.out.println(key +" -> "+myMap.get(key)));
Output:
2020-01-30 02:45:33 -> myself2
2020-01-31 10:45:33 -> myself
2020-01-31 10:45:48 -> myself
You can convert date object to string and compare them. But keep in mind comparing strings is inefficient
A better solution can be achieved by converting string date to an object of Date using map.
You can provide more details about your data and requirements and I will try to help you.
readable format
Please find ready to be tested code below.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.maxBy;
public class ListOfMapStreaming {
public static void main(String[] args) {
Map<String,String> map1 = new HashMap<>();
map1.put("myself", "2020-06-11 10:45:33");
Map<String,String> map2 = new HashMap<>();
map2.put("myself", "2020-06-11 18:35:33");
Map<String,String> map3 = new HashMap<>();
map3.put("myself2", "2020-01-30 02:45:33");
Map<String,String> map4 = new HashMap<>();
map4.put("myself2", "2020-03-15 10:33:48");
List<Map<String, String>> list1 = Arrays.asList(map1, map2, map3, map4);
Date now = new Date();
System.out.println(now.toString());
Predicate<Tuple> onlyWithinLast5Mintues = tuple -> ((now.getTime()- tuple.getDate().getTime()) / (1000*60)) < 5;
Map<String, Optional<Tuple>> result = list1.stream()
.flatMap(map -> map.entrySet().stream())
.map(entry -> convertToTupleObject(entry))
.filter(onlyWithinLast5Mintues)
// most recent within last minutes
.collect(groupingBy(Tuple::getUserName, maxBy(Comparator.comparing(Tuple::getDate))));
// Now you can convert Date object back to String form if you want :)
System.out.println(result);
}
private static Tuple convertToTupleObject(Map.Entry<String, String> x) {
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
try {
date = dateFormatter.parse(x.getValue());
} catch (ParseException e) {
e.printStackTrace();
}
return new Tuple(x.getKey(), date);
}
private static class Tuple {
String userName;
Date date;
public Tuple() {
}
public Tuple(String userName, Date date) {
this.userName = userName;
this.date = date;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
#Override
public String toString() {
return "Tuple{" +
"userName='" + userName + '\'' +
", date=" + date +
'}';
}
}
}
Please comment if you have any doubts regarding implementation.

sorting list of using java8 [duplicate]

This question already has answers here:
How to sort a List/ArrayList?
(21 answers)
Closed 2 years ago.
I have a list like ArrayList<DateFrequency>
private static ArrayList<DateFrequency> getUnsortedDateFrequencyList()
{
ArrayList<DateFrequency> list = new ArrayList<>();
list.add( new DateFrequency(05/10/2020, "60-DAYS") );
list.add( new DateFrequency(05/10/2020, "30-DAYS") );
list.add( new DateFrequency(05/11/2020, "30-DAYS") );
list.add( new DateFrequency(05/12/2020, "60-DAYS") );
list.add( new DateFrequency(05/11/2020, "90-DAYS") );
}
I need Sorted List as
05/10/2020, "30-DAYS"
05/10/2020, "60-DAYS"
05/11/2020, "30-DAYS"
05/11/2020, "90-DAYS"
05/12/2020, "60-DAYS"
You can use java 8 stream to achieve the same as shown below.
List<String> sortedList = list.stream().sorted().collect(Collectors.toList());
Use DateTimeFormatter to create the required pattern so that you can parse the date string to LocalDate.
Use Comparator to compare the DateFrequency objects on parsed date string.
Demo:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class DateFrequency {
private String date;
private String frequency;
public DateFrequency(String date, String frequency) {
this.date = date;
this.frequency = frequency;
}
public String getDate() {
return date;
}
#Override
public String toString() {
return "DateFrequency [date=" + date + ", frequency=" + frequency + "]";
}
}
public class Main {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
List<DateFrequency> list = new ArrayList<DateFrequency>();
list.add(new DateFrequency("05/10/2020", "60-DAYS"));
list.add(new DateFrequency("05/10/2020", "30-DAYS"));
list.add(new DateFrequency("05/11/2020", "30-DAYS"));
list.add(new DateFrequency("05/12/2020", "60-DAYS"));
list.add(new DateFrequency("05/11/2020", "90-DAYS"));
// Sort on dates in ascending order
Collections.sort(list, Comparator.comparing((df) -> LocalDate.parse(df.getDate(), formatter)));
// Display the sorted result
for (DateFrequency df : list) {
System.out.println(df);
}
}
}
Output:
DateFrequency [date=05/10/2020, frequency=60-DAYS]
DateFrequency [date=05/10/2020, frequency=30-DAYS]
DateFrequency [date=05/11/2020, frequency=30-DAYS]
DateFrequency [date=05/11/2020, frequency=90-DAYS]
DateFrequency [date=05/12/2020, frequency=60-DAYS]
Assumptions:
The date strings are in the format dd/MM/yyyy. If not, change it in the object, formatter accordingly.
The sorting is required only on dates in ascending order.

Convert a List Partially JSON Formatted Data into a Map

I have a following Object Data as below
[{
name = john , id=1,email id : [abc#gmail.com,def#gmail.com,ghi#gmail.com]
},{
name = joy, id=2,email id : [jkl#gmail.com,mno#gmail.com,pqr#gmail.com]
}]
I want the following to be converted to Map
as
{john=[abc#gmail.com,def#gmail.com,ghi#gmail.com]},{joy= [jkl#gmail.com,mno#gmail.com,pqr#gmail.com]}
in Java how will I be able to achieve this.
Kindly help me with this
no easy way of doing this, so much hard manual work with so many possibilities for errors, you can use this. as long as your data is EXACTLY as shown then this should work.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Person {
int id;
String name;
List<String> emails;
public static Person parse(String s) throws Exception {
Person person = new Person();
person.name = s.substring(0, s.indexOf(",")).replaceAll(".+= ?", "");
s = s.substring(s.indexOf(",") + 1, s.length());
person.id = Integer.parseInt(s.substring(0, s.indexOf(",")).replaceAll(".+= ?", "").trim());
s = s.substring(s.indexOf(",") + 1, s.length());
s = s.replaceAll(".+: ?", "").replaceAll("[ \\[\\]]", "");
person.emails = Arrays.asList(s.split(","));
return person;
}
public static List<Person> parseMany(String s) throws Exception {
List<Person> people = new ArrayList<Person>();
while (true){
int openBracket = s.indexOf("{");
int closeBracket = s.indexOf("}");
if(closeBracket == -1){
break;
}
people.add(parse(s.substring(openBracket, closeBracket)));
s = s.substring(closeBracket + 1, s.length());
}
return people;
}
public static void main(String[] args) throws Exception {
String s = "[{name = john , id=1,email id : [abc#gmail.com,def#gmail.com,ghi#gmail.com]},{name = joy, id=2,email id : [jkl#gmail.com,mno#gmail.com,pqr#gmail.com]}]";
List<Person> people = parseMany(s);
Map<String, List<String>> map = new HashMap<String, List<String>>();
for(Person person : people){
map.put(person.name, person.emails);
}
System.out.println(map);
}
}
OUTPUT:
{joy=[jkl#gmail.com, mno#gmail.com, pqr#gmail.com], john
=[abc#gmail.com, def#gmail.com, ghi#gmail.com]}
NOTE:
I didn't bother to comment this code, because problem doesn't follow any standard procedures.

Categories

Resources