The following program reads and stores data from a text file of the form
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
to an array list. Then I store the weather object in another class. The first method calculates average temperature between two dates that is user input and the second method calculates the percentage of "G":s between the two dates.
/**
* 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);
Weather weather = new Weather(date, time, temperature, quality);
weatherData.add(weather);
}
}
private List<String> parseData(String s) {
return Arrays.asList(s.split(";"));
}
/**
* Search for average temperature for all dates between the two dates (inclusive).
* Result is sorted by date.
*/
public Map<LocalDate, Double> avarageTemperatures(LocalDate dateFrom, LocalDate dateTo) {
Map<LocalDate, Double> Averagetemperature = weatherData.stream().filter(weather -> !weather.getDate().isAfter(dateTo) && !weather.getDate().isBefore(dateFrom))
.collect(Collectors.groupingBy(Weather::getDate,
Collectors.averagingDouble(Weather::getTemperature)));
return Averagetemperature;}
/**
* Search for percentage of approved values between the two dates (inclusive).
*/
public Double approvedValues(LocalDate dateFrom, LocalDate dateTo) {
double percentageApproved = weatherData.stream().filter(weather -> !weather.getDate().isAfter(dateTo) && !weather.getDate().isBefore(dateFrom))
.mapToInt(obj -> obj.getQuality().equals("G") ? 1 : 0)
.summaryStatistics()
.getAverage();
return percentageApproved;
}
}
Now, instead of implementing maps to the methods I would like to store the data from the file to a hashmap instead of a list (for efficiency). Is there any easy ways to do this without having to redo the methods completely? I tried this:
public class WeatherDataHandler {
public void loadData1(String filePath) throws IOException {
List<String> fileData = Files.readAllLines(Paths.get(filePath));
Map<LocalDate, List<Weather>> weatherData = new HashMap<LocalDate,List<Weather>>();{
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(";"));
}
But how can I access the entries of the map in the methods?
A hash map will not work for your use case. HashMap helps you find the matching value, given a key. For example, you could make a HashMap that gives you the matching weather data given one date. But, you can't use a HashMap to answer the query "give me all weather data between these two dates" without iterating over the date range. And that's what you need for your avarageTemperatures
and approvedValues
methods.
What you could use instead, is TreeMap
. It will require changing your program in a couple of places.
The variable declaration:
private List<Weather> weatherData = new ArrayList<>();
Has to become:
private NavigableMap<LocalDate, List<Weather>> weatherData = new TreeMap<>();
This is a "map from date to list of weather data" because there are more than one data item per date. If you can change that, it would be simpler.
Next, instead of using weatherData.add(weather)
you need to use:
weatherData.computeIfAbsent(date, key -> new ArrayList<>()).add(weather);
Again, this is more complicated because there may be more than one item per date.
Lastly, where you access the data through iteration:
weatherData.stream().filter(weather -> !weather.getDate().isAfter(dateTo) && !weather.getDate().isBefore(dateFrom))
You would leverage the subMap
method instead, which looks up a key range directly:
weatherData.subMap(dateFrom, dateTo).values().stream().flatMap(List::stream)