Counting the values of a map

Viewed 352

The following map contains data of the form:

1946-01-12;13:00:00;0.3;G
1946-01-12;18:00:00;-2.8;Y
1946-01-13;07:00:00;-6.2;G
1946-01-13;13:00:00;-4.7;G

The dates are the keys.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NavigableMap;
import java.util.TreeMap;

public class WeatherDataHandler {
    private NavigableMap<LocalDate, List<Weather>> weatherData =
            new TreeMap<>();
    
    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);
            Weather weather =
                    new Weather(date, time, temperature, quality);
            
            List<Weather> entries;
            entries = new ArrayList<Weather>();
            if (weatherData.get(date) == null) {
                entries.add(weather);
                weatherData.put(date, entries);
            } else {
                entries = weatherData.get(date);
                entries.add(weather);
            }
        }
    }
    
    private List<String> parseData(String str) {
        return Arrays.asList(str.split(";"));
    }
}

Now, I want to implement a method that counts the number of entries of every key, or in other words, the number of times every date occur in the list. It shall return all dates between two dates (that is user input) and the number of values of every key. I started with the following code

/**
 * Search for missing values between the two dates (inclusive) assuming there
 * should be 24 measurement values for each day (once every hour). Result is
 * sorted by date.
 */
public Map<LocalDate, Integer> missingValues(LocalDate dateFrom, LocalDate dateTo) {

    Map<LocalDate, Integer> counts = weatherData.subMap(dateFrom , dateTo)
            .values().stream()
            .flatMap(List::stream)
            .filter(p -> p.getValue()
            .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
    }
}

but I am having troubles with the filter and collectors method. How can I finish this?

3 Answers

Use Collectors.groupingBy to group by date of Weather class and Collectors.counting() to count the size of every group.

Map<LocalDate, Long> counts = weatherData.subMap(dateFrom ,dateTo)
                                         .values()
                                         .stream()
                                         .flatMap(List::stream)
                        .collect(Collectors.groupingBy(p -> p.getDate(), Collectors.counting()));

What you effectively need is to group by the date as the key element of the map. In the value, you want how many times each key has occurred.

For this, you will have to use Collectors.groupingBy while collecting the map. and for the value, it would be simply Collectors.counting(). Please check the code snippet below for more detail:

Map<LocalDate, Long> countMap = weatherData
                                .subMap(dateFrom ,dateTo)
                                .values()
                                .stream()
                                .flatMap(List::stream)
                                .collect(Collectors.groupingBy(p -> p.getDate(), Collectors.counting()));

Since you are using streams to simplify your code you may also want to do the following.

Instead of doing

    List<Weather> entries;
    entries = new ArrayList<Weather>();
    if (weatherData.get(date) == null) {
         entries.add(weather);
         weatherData.put(date, entries);
    } else {
        entries = weatherData.get(date);
        entries.add(weather);
    }

You can just do

     weatherData.computeIfAbsent(date, k->new ArrayList<>()).add(weather);

It says that if the key date is not there, put in a new ArrayList as the value. In any event, return the value. Since the value is the list that was either just entered or already there, you can then simply add the weather instance to the list.

The compiler already knows the type of list since it derived that from your NavigableMap instance.

If you want to check it out first, try this simple example.

Map<Integer,List<Integer>> map = new HashMap<>();
System.out.println(map);
map.computeIfAbsent(10, k-> new ArrayList<>()).add(20);
System.out.println(map);
map.computeIfAbsent(10, k-> new ArrayList<>()).add(30);
System.out.println(map);

computeIfAbsent has been available since Java 1.8

Related