Reputation: 153
I have a log file which looks like this:
LSR2019-07-12_12:07:21.554
KMH2019-07-12_12:09:44.291
RGH2019-07-12_12:29:28.352
RGH2019-07-12_12:33:08.603
I have a parser which parses data to abbreviation/date/time:
public Map <String, ?> parse() throws IOException {
try (Stream<String>lines = Files.lines(path)){
return lines.collect(Collectors.toMap(
string -> string.substring(0,3),
string -> new DateAndTimeInfo(LocalTime.from(DateTimeFormatter.ofPattern("HH:mm:ss.SSS").parse((string.substring(3).split("_")[1]))),
LocalDate.parse(string.substring(3).split("_")[0], DateTimeFormatter.ofPattern("yyyy-MM-dd"))),
(string1, string2)-> ??? )); //Struggle here
After parsing it creates a map that contains abbreviations as keys and instances of DateAndTimeInfo class. The class looks like this:
public class DateAndTimeInfo {
private List<LocalTime> localTime;
private List<LocalDate> localDate;
public DateAndTimeInfo(LocalTime localTime, LocalDate localDate) {
this.localTime = Arrays.asList(localTime);
this.localDate = Arrays.asList(localDate);
}
public List<LocalTime> getLocalTime() {
return this.localTime;
}
public List<LocalDate> getLocalDate() {
return this.localDate;
}
public void addAnotherLapTime(LocalTime localtime, LocalDate localDate) {
this.localTime.add(localtime);
this.localDate.add(localDate);
}
}
Everything works fine until the log file has a duplicate abbreviation. As soon as a duplicate key appears I want the data to be stored inside the DateAndTimeInfo object, which was created when the first duplicate was parsed. To do so I have the addAnotherLapTime()
method. The problem is I can't figure out how to write it in my stream.
Upvotes: 2
Views: 333
Reputation: 40047
You can try this. I added lambdas to make it a little cleaner to view. Basically it uses the merge function of toMap to copy list from the newly created class to the already existing class. Here are the mods I made to your class.
DateAndTimeInfo
String[] lines = {
"LSR2019-07-12_12:07:21.554",
"KMH2019-07-12_12:09:44.291",
"KMH2019-07-12_12:09:44.292",
"RGH2019-07-12_12:29:28.352",
"RGH2019-07-12_12:33:08.603",
"RGH2019-07-12_12:33:08.604"};
Function<String, LocalTime> toLT = (str) -> LocalTime
.from(DateTimeFormatter.ofPattern("HH:mm:ss.SSS")
.parse((str.substring(3).split("_")[1])));
Function<String, LocalDate> toLD = (str) -> LocalDate.parse(
str.substring(3).split("_")[0],
DateTimeFormatter.ofPattern("yyyy-MM-dd"));
Map<String, DateAndTimeInfo> map = lines
.collect(Collectors.toMap(
string -> string.substring(0,3),
string-> new DateAndTimeInfo(toLT.apply(string), toLD.apply(string)),
(dti1,dti2)-> dti1.copy(dti2)))
class DateAndTimeInfo {
private List<LocalTime> localTime = new ArrayList<>();
private List<LocalDate> localDate = new ArrayList<>();
public DateAndTimeInfo(LocalTime lt, LocalDate ld) {
localTime.add(lt);
localDate.add(ld);
}
public DateAndTimeInfo copy(DateAndTimeInfo dti) {
this.localTime.addAll(dti.localTime);
this.localDate.addAll(dti.localDate);
return this;
}
public String toString() {
return localTime.toString() + "\n " + localDate.toString();
}
}
For the given test data, it prints.
RGH=[12:29:28.352, 12:33:08.603, 12:33:08.604]
[2019-07-12, 2019-07-12, 2019-07-12]
KMH=[12:09:44.291, 12:09:44.292]
[2019-07-12, 2019-07-12]
LSR=[12:07:21.554]
[2019-07-12]
Note. Did you consider of creating a map like the following:
Map<String, List<DateAndTimeInfo>>
and storing just the date and time in each class as fields? You could get them with getters. It would be trivial to implement. So the value of the key would be a list of DateAndTimeInfo
objects.
Map<String, List<DateAndTimeInfo>> map = lines
.collect(Collectors.groupingBy(str->str.substring(0,3),
Collectors.mapping(str->new DateAndTimeInfo(toLT.apply(str),
toLD.apply(str)), Collectors.toList())));
Upvotes: 1
Reputation: 298539
Since the combination of values will end up in List
objects, this is a task for the groupingBy
collector.
But first, you have to fix the DateAndTimeInfo
class. It’s only constructor
public DateAndTimeInfo(LocalTime localTime, LocalDate localDate) {
this.localTime = Arrays.asList(localTime);
this.localDate = Arrays.asList(localDate);
}
creates fixed size lists, so the method
public void addAnotherLapTime(LocalTime localtime, LocalDate localDate) {
this.localTime.add(localtime);
this.localDate.add(localDate);
}
will fail with exceptions.
When you use
public class DateAndTimeInfo {
private List<LocalTime> localTime;
private List<LocalDate> localDate;
public DateAndTimeInfo() {
localTime = new ArrayList<>();
localDate = new ArrayList<>();
}
public List<LocalTime> getLocalTime() {
return this.localTime;
}
public List<LocalDate> getLocalDate() {
return this.localDate;
}
public void addAnotherLapTime(LocalTime localtime, LocalDate localDate) {
this.localTime.add(localtime);
this.localDate.add(localDate);
}
}
instead, you can collect the map like
public Map<String, DateAndTimeInfo> parse() throws IOException {
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSS");
try(Stream<String>lines = Files.lines(path)){
return lines.collect(Collectors.groupingBy(
string -> string.substring(0,3),
Collector.of(DateAndTimeInfo::new, (info,str) -> {
LocalDateTime ldt = LocalDateTime.parse(str.substring(3), f);
info.addAnotherLapTime(ldt.toLocalTime(), ldt.toLocalDate());
}, (info1,info2) -> {
info1.getLocalDate().addAll(info2.getLocalDate());
info1.getLocalTime().addAll(info2.getLocalTime());
return info1;
})));
}
}
groupingBy
allows to specify a collector for the groups and this solution creates a new ad-hoc Collector
for the DateAndTimeInfo
objects.
You may consider whether you really want to keep the dates and times in different lists.
The alternative would be:
public class DateAndTimeInfo {
private List<LocalDateTime> localDateTimes;
public DateAndTimeInfo(List<LocalDateTime> list) {
localDateTimes = list;
}
public List<LocalDateTime> getLocalDateTimes() {
return localDateTimes;
}
// in case this is really needed
public List<LocalTime> getLocalTime() {
return localDateTimes.stream()
.map(LocalDateTime::toLocalTime)
.collect(Collectors.toList());
}
public List<LocalDate> getLocalDate() {
return localDateTimes.stream()
.map(LocalDateTime::toLocalDate)
.collect(Collectors.toList());
}
}
and then
public Map<String, DateAndTimeInfo> parse() throws IOException {
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSS");
try(Stream<String>lines = Files.lines(path)){
return lines.collect(Collectors.groupingBy(
string -> string.substring(0,3),
Collectors.collectingAndThen(
Collectors.mapping(s -> LocalDateTime.parse(s.substring(3), f),
Collectors.toList()),
DateAndTimeInfo::new)));
}
}
Upvotes: 4