1

I'm currently trying to figure out how to calculate the average temperature for a day. The day usually has 24 measurements, 00:00 01:00... etc. The file contains information divided by ';'. Like this:

2020-12-30;18:00:00;1.9;G
2020-12-30;19:00:00;1.3;G
2020-12-30;20:00:00;1.1;G
2020-12-30;21:00:00;1.0;G
2020-12-30;22:00:00;1.1;G
2020-12-30;23:00:00;1.3;G
2020-12-31;00:00:00;1.1;G
2020-12-31;01:00:00;1.4;G
2020-12-31;02:00:00;1.9;G
2020-12-31;03:00:00;1.9;G
2020-12-31;04:00:00;2.4;G

I managed to store the data in a List of the constructed type Data: This is where I read the file and call the constructor to create an object with the following information, LocalDateTime, Degrees, whether the measurement was approved or not.

public void loadData(String filePath) throws IOException {
    List<String> fileData = Files.readAllLines(Paths.get(filePath));
    for (String fileDatum : fileData) {
        List<String> list = Arrays.asList(fileDatum.split(";"));
        LocalDateTime localDateTime = LocalDateTime.of(
                LocalDate.parse(list.get(0)), LocalTime.parse(list.get(1)));
        double temp = Double.parseDouble(list.get(2));
        String kontroll = list.get(3);
        Data data = new Data(localDateTime, kontroll, temp);
        vaderData.add(data);
    }
}

This is where I am trying to calculate the average temperature of a day. I manage to calculate for one day, but don't really understand how I can go on to the next day without having to iterate the entire list again. I also experimented with using the Collectors class but had no luck there neither.

public List<String> averageTemperatures(LocalDate dateFrom,
                                        LocalDate dateTo) {
    double sum = 0;
    int count = 0;
    for (Data average : vaderData) {
        if (dateFrom.isEqual(average.getDate().toLocalDate())) {
            sum = sum + average.getTemp();
            count++;
        }
    }
    /*
    Map<LocalDate, Double> Averagetemperature = vaderData.stream()
            .filter(Data -> !Data.getDate().toLocalDate().isAfter(dateTo)
                    && !Data.getDate().toLocalDate().isBefore(dateFrom))
            .collect(Collectors.groupingBy(Data::getDate,
                    Collectors.averagingDouble(Data::getTemp)));
    */
    return null;
}
Community
  • 1
  • 1

2 Answers2

0

If you want to get averages by day in a range you can use the stream api with grouping, filtering and mapping to do something like this:

Map<LocalDate, Double> averages = vaderData.stream()
        .collect(Collectors.groupingBy(d -> d.localDateTime.toLocalDate()))
        .entrySet() // set of map entries with key as local date and value as list of data
        .stream() // stream of map entries
        // filter such that only dates >= from and < to are considered
        .filter(e -> (e.getKey().isEqual(from) || e.getKey().isAfter(from)) && e.getKey().isBefore(to))
        // map entries such that key is local date and value is average temp for that day
        .map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), e.getValue().stream()
                .mapToDouble(d -> d.temp)
                .average()
                .orElse(Double.NaN)))
        // collect it all as a map with key as the day and value as the average
        .collect(Collectors.toMap(
                AbstractMap.SimpleEntry::getKey,
                AbstractMap.SimpleEntry::getValue));

Assumptions:

The data class is defined as follows

class Data {
    final LocalDateTime localDateTime;
    final String kontroll;
    final double temp;

    Data(LocalDateTime localDateTime, String kontroll, double temp) {
        this.localDateTime = localDateTime;
        this.kontroll = kontroll;
        this.temp = temp;
    }
}

The from / to parameters used in the above are something like

LocalDate from = LocalDate.parse("2020-12-30");
LocalDate to = LocalDate.parse("2020-12-31");

And they are such that from is inclusive and to is exclusive.

With the above input you'll get

{2020-12-30=1.2833333333333334}

Corresponding with the average temperature for 2020-12-30 with the provided data.

Edit

From the comments, to get counts and other information, look at the summaryStatistics() method. More info here: DoubleSummaryStatistics.

Example

after the mapToDouble bit, instead of average(), with summaryStatistics you would have information for min, max, count, sum and average.

The return type on it at that point isn't a Map<LocalDate, Double> anymore, it would be a Map<LocalDate, DoubleSummaryStatistics>.

geco17
  • 5,152
  • 3
  • 21
  • 38
  • This works! Thank you Although one more question. Is it more effiecient to have it as a map instead of a list? – Displayer4321 Mar 21 '21 at 11:06
  • I would keep it as a map since it's a bunch of map entries. I don't see the point in having a bunch of map entries in a list. If a list is preferrable you can define a class to keep the info with two fields, one for the date and the other for the average. – geco17 Mar 21 '21 at 11:10
  • Ah I see... I'm kinda new to the collector class, been trying to understand what you did with the code there by making a few adjustments for learning purposes. I reckon I should be able to count the occurences of times each date appears aswell? I saw it had a counter but couldnt make it work with the filter on. I managed to count the dates without filtering, but then I just counted the entirty of the list. – Displayer4321 Mar 21 '21 at 15:13
  • I updated the answer if you need more stats than just average – geco17 Mar 21 '21 at 16:46
0

You can collect a TreeMap of Lists from these strings. Key - LocalDate, value - List<Double>. Then you can select a subMap from that map between the specified dates and take average, min, or max values from the lists, or filter them somehow:

String[] info = {
        "2020-12-30;18:00:00;1.9;G",
        "2020-12-30;19:00:00;1.3;G",
        "2020-12-30;20:00:00;1.1;G",
        "2020-12-30;21:00:00;1.0;G",
        "2020-12-30;22:00:00;1.1;G",
        "2020-12-30;23:00:00;1.3;G",
        "2020-12-31;00:00:00;1.1;G",
        "2020-12-31;01:00:00;1.4;G",
        "2020-12-31;02:00:00;1.9;G",
        "2020-12-31;03:00:00;1.9;G",
        "2020-12-31;04:00:00;2.4;G"};
TreeMap<LocalDate, List<Double>> map = Arrays.stream(info)
        // Stream<String[]>
        .map(row -> row.split(";"))
        .collect(Collectors.toMap(
                // key - LocalDate
                arr -> LocalDate.parse(arr[0]),
                // value - List<Double>
                arr -> List.of(Double.parseDouble(arr[2])),
                // concatenate values with the same key
                (list1, list2) -> {
                    ArrayList<Double> list =
                            new ArrayList<>();
                    list.addAll(list1);
                    list.addAll(list2);
                    return list;
                }, TreeMap::new));
// output a sub-map between two dates inclusive with average values
LocalDate fromDate = LocalDate.parse("2020-12-30");
LocalDate toDate = LocalDate.parse("2020-12-31");
map.subMap(fromDate, true, toDate, true)
        .forEach((key, value) -> {
            double avg = value.stream()
                    .mapToDouble(d -> d)
                    .average().orElse(0.0);
            System.out.println(key + " " + avg);
        });

Output:

2020-12-30 1.2833333333333334
2020-12-31 1.7399999999999998

See also: Convert a map of lists into a list of maps