2

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.

IceTeaGreen
  • 133
  • 12
  • Does this answer your question? [Ignore duplicates when producing map using streams](https://stackoverflow.com/questions/32312876/ignore-duplicates-when-producing-map-using-streams) – Giri Oct 16 '20 at 13:53
  • Map can't have duplicate keys, you will need to come up with another way or data structure to resolve it. – Aniket Sahrawat Oct 16 '20 at 13:53
  • @AniketSahrawat, I know. My goal is to have the same Key, but the value (the instance of the DateAndtimeInfo class) should have different data. – IceTeaGreen Oct 16 '20 at 13:57
  • One bad tiding: unless you exclude Day-Time-Savings (summer time), there typically can be a repitition of the times between 02:00 and excl. 03:00 AM. – Joop Eggen Oct 16 '20 at 13:57
  • @Giri. Thank you, I've already checked this post. My problem is that I can't figure out how to write this place in my code. listOrders.collect(Collectors.toMap(Order::getCode, o -> o.getQuantity(), (o1, o2) -> o1 + o2)); – IceTeaGreen Oct 16 '20 at 13:58

2 Answers2

4

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)));
    }
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • nice. can't you replace `Arrays.asList` with `Collections.singletonList`? or you were simply following OPs code? – Eugene Oct 16 '20 at 20:05
  • 1
    @Eugene the solution does not contain `Arrays.asList`. It's only in the description of a problem with the OP's code, which is of course cited as-is. Using `singletonList` instead would bear the same problem though. – Holger Oct 17 '20 at 08:01
1

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.

  • the constructor puts the values in the lists.
  • added a copy constructor to copy one list to the other in another instance of DateAndTimeInfo
  • Added a toString method.
    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())));
WJS
  • 36,363
  • 4
  • 24
  • 39
  • that's exactly what I wanted! Worked like a charm – IceTeaGreen Oct 16 '20 at 21:28
  • @IceTeaGreen *that's exactly what I wanted! Worked like a charm – IceTeaGreen* Just curious why you changed your mind? Did I overlook an error or something? – WJS Oct 17 '20 at 14:25
  • no, I used your variant and it worked. If you are talking about my accepting your answer as the right one, the thing is I'm new here and thought that several answers can be accepted as the right ones. So I put a tick near your post and near another one too and didn't notice the tick near your post disappeared) I fixed it now) – IceTeaGreen Oct 19 '20 at 07:22
  • @IceTeaGreen Please don't misunderstand. I just wanted to make certain I didn't make a mistake. Holger's answer is excellent and is superior in several ways. Especially converting the date time to a a single object and then passing those to your class. You need to accept the one that works for you. – WJS Oct 19 '20 at 12:11