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.
Related
lets say I have:
bob:V
bob:A
bob:B
bob:C
bob:C
sally:B
sally:C
sally:A
steve:A
steve:B
steve:C
how do I store:
the values as:
bob={V,A,B,C,C}, sally={B,C,A}, steve={A,B,C}
and for any guy who has a sequence A,B,C repeated how do I get that person name?
I am fairly new to Java and Im trying to implement this scenario, as I dont see anything like this in this communtiy.
here is my final answer: first stored the list into a map and then used collectors to loop through and map it to their respective attributes.
public class Solution{
static List<String> doWork(List<LogItem> eventsInput) {
Map<String, String> personMap = eventsInput.stream()
.collect(Collectors.toMap(LogItem::getUserId, p -> Character.toString(p.getEventChar()), String::concat));
System.out.println("person map is \n" + personMap);
BiPredicate<Entry<String, List<String>>, String> contains =
(entry, attr) -> entry.getValue().stream()
.collect(Collectors.joining()).contains(attr);
String attributes = "ABC";
List<String> results = personMap.entrySet().stream()
.filter(e -> e.getValue().contains(attributes))
.map(Entry::getKey).collect(Collectors.toList());
return results;
}
public static void main(String[] args){
List<LogItem> exampleInputItems = new ArrayList<>();
exampleInputItems.add(new LogItem("bob", 'V'));
exampleInputItems.add(new LogItem("bob", 'A'));
exampleInputItems.add(new LogItem("steve", 'A'));
exampleInputItems.add(new LogItem("bob", 'B'));
exampleInputItems.add(new LogItem("bob", 'C'));
exampleInputItems.add(new LogItem("bob", 'C'));
exampleInputItems.add(new LogItem("steve", 'B'));
exampleInputItems.add(new LogItem("sally", 'B'));
exampleInputItems.add(new LogItem("steve", 'C'));
exampleInputItems.add(new LogItem("sally", 'C'));
exampleInputItems.add(new LogItem("sally", 'A'));
List<String> returnedNames = doWork(exampleInputItems);
if (returnedNames.size() != 2) {
throw new RuntimeException("Wrong number of names found. Found: " + returnedNames);
}
if (!returnedNames.contains("bob")) {
throw new RuntimeException("Did not find \"bob\" in the returnedNames: " + returnedNames);
}
if (!returnedNames.contains("steve")) {
throw new RuntimeException("Did not find \"steve\" in the returnedNames: " + returnedNames);
}
System.out.println("The example passed.");
}
static class LogItem {
public String userId;
public char eventChar;
public LocalDateTime dateTime;
LogItem(String userId, char eventChar) {
this.userId = userId;
this.eventChar = eventChar;
dateTime = LocalDateTime.now();
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public char getEventChar() {
return eventChar;
}
public void setEventChar(char eventChar) {
this.eventChar = eventChar;
}
public LocalDateTime getDateTime() {
return dateTime;
}
public void setDateTime(LocalDateTime dateTime) {
this.dateTime = dateTime;
}
#Override
public String toString() {
return "LogItem [userId=" + userId + ", eventChar=" + eventChar + ", dateTime=" + dateTime + ", getUserId()="
+ getUserId() + ", getEventChar()=" + getEventChar() + ", getDateTime()=" + getDateTime() + "]";
}
}
}
}
First, I would store the attributes in a Map<String,String>. This will make it easier to filter the attributes later. I am using a record in lieu of a class but a class would work as well.
record Person(String getName, String getAttribute) {
}
Create the list of Person objects
List<Person> list = List.of(new Person("bob", "V"),
new Person("bob", "A"), new Person("bob", "B"),
new Person("bob", "C"), new Person("bob", "C"),
new Person("sally", "B"), new Person("sally", "C"),
new Person("sally", "A"), new Person("steve", "A"),
new Person("steve", "B"), new Person("steve", "C"));
Now create the map. Simply stream the list of people and concatenate the attributes for each person.
Map<String, String> personMap = list.stream()
.collect(Collectors.toMap(Person::getName,
Person::getAttribute, String::concat));
The map will look like this.
bob=VABCC
steve=ABC
sally=BCA
Now grab the name based on an attribute string.
Now stream the entries of the map and pass the entry whose value contains the attribute string. Then retrieve the key (name) and return as a list of names.
String attributes = "ABC";
ListString> results = personMap.entrySet().stream()
.filter(e -> e.getValue().contains(attributes))
.map(Entry::getKey).collect(Collectors.toList());
System.out.println(results);
prints
[bob, steve]
Alternative approach using Map<String, List<String>>
Group the objects by name but the values will be a list of attributes instead of a string.
Map<String, List<String>> personMap = list.stream()
.collect(Collectors.groupingBy(Person::getName,
Collectors.mapping(Person::getAttribute,
Collectors.toList())));
The map will look like this.
bob=[V, A, B, C, C]
steve=[A, B, C]
sally=[B, C, A]
To facilitate testing the attributes, a BiPredicate is used to stream the list and concatinate the attributes and then check and see if it contains the attribute string.
BiPredicate<Entry<String, List<String>>, String> contains =
(entry, attr) -> entry.getValue().stream()
.collect(Collectors.joining()).contains(attr);
As before, stream the entry set of the map and apply the filter to pass those entries which satisfy the condition. In this case, the filter invokes the BiPredicate.
String attributes = "ABC";
List<String> results = personMap.entrySet().stream()
.filter(e->contains.test(e, attributes))
.map(Entry::getKey).collect(Collectors.toList());
System.out.println(results);
prints
[bob, steve]
Update Answer
To work with Character attributes, you can do the following using the first example.
Map<String, String> personMap2 = list.stream()
.collect(Collectors.toMap(Person::getName,
p -> Character.toString(p.getAttribute()),
String::concat));
Imo, it would be easier, if possible to change your attribute types to string.
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);
}
}
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.
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);
}
I have specific text file looking like this:
name: meeting_name1
description:
04/18/2012 00:00:00
05/18/2012 00:00:00
... (more dates)
07/18/2012 00:00:00
name: meeting_name2
description: some_desc
04/18/2012 00:00:00
05/18/2012 00:00:00
... (more dates)
07/18/2012 00:00:00
(etc)
I have java object looking like this:
class Meeting {
String name;
String description;
List<Date> dates;
}
My point is to read the file, parse values, create objects and save them to database.
I can read the file line by line and convert it to List<String>, ie. all data together.
`I can make and fill one java object with values and save it to database.
My issue here is how to find out that I'm at the end of dates and lines (name: meeting_name2) of new object begin.
So I could make something like List<List<String>> where List<String> would be equal to one object, ie. List<Meeting>?
Not sure if its understandable, sorry for formatting.
Assumption that you could read the file data to List variable. (See above answer)
List<String> lines = Files.readAllLines(Paths.get("FILE_NAME"));
Now, you can see below code as a demo. It is the simple loop and if else statament.
Hope it will help you.
public static void main(String[] args) throws ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
List<String> data = new ArrayList<>();
data.add("name: meeting_name1");
data.add("description: some_desc");
data.add("07/18/2012 00:00:00");
data.add("07/18/2012 00:00:00");
data.add("name: meeting_name2");
data.add("description: some_desc");
data.add("07/18/2012 00:00:00");
List<Meeting> result = new ArrayList<>();
Meeting temp = null;
for (String line : data) {
if (line.startsWith("name:")) {
temp = new Meeting(line.split(":")[1].trim());
result.add(temp);
} else if (line.startsWith("description:")) {
temp.setDescription(line.split(":")[1].trim());
} else {
temp.getDates().add(simpleDateFormat.parse(line)); // Use date for
}
}
System.out.println(result.get(0).getName() + ": " + result.get(0).getDates().size()); // meeting_name1: 2
System.out.println(result.get(1).getName() + ": " + result.get(1).getDates().size()); // meeting_name2: 1
}
static class Meeting {
String name;
String description;
List<Date> dates;
public String getName() {
return name;
}
public List<Date> getDates() {
return dates;
}
Meeting(String name) {
this.name = name;
this.dates = new ArrayList<>();
}
public void setDescription(String description) {
this.description = description;
}
}
One possibility would be to read all lines first. You would not need to worry about the end of lines with:
List<String> lines = Files.readAllLines(Paths.get("FILE_NAME"));
then iterarate through the array,
if a line starts with "name:" you make a new object and add the data like that:
List<Meeting> meetings = new ArrayList();
Meeting currentMeeting;
for (String line : lines) {
if(line.startsWith("name:"))
{
currentMeeting = new Meeting();
meetings.add(currentMeeting);
//...add data (name)
}
//...add more data (description and dates)
}