2016-01-24 90 views
1

給定一個TimeEntry對象列表,我想將它們分組爲Day和Hour對象。Collectors.grouping進入對象列表?

其中Day包含Hour對象的列表,Hour包含TimeEntry對象的列表,表示它所代表的小時。

日和小時還保留在各自列表中的TimeEntry持續時間的總和。如果可能的話,也可以將日期名稱設爲Day。 TimeEntry列表按「開始」進行排序。

public class TimeEntry { 
    private LocalDateTime start; 
    private Duration duration; 

    public Integer getDay() { return this.start.get(ChronoField.DAY_OF_MONTH); } 
    public Integer getHour() { return this.start.get(ChronoField.HOUR_OF_DAY); } 
    public String getDayName() { 
     return this.start.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault()); 
    } 
} 

public class Day { 
    private Integer dayNr; 
    private String dayName; 
    private Duration sumOfHours; 
    private List<Hour> hours; 
} 

public class Hour { 
    private Integer hourNr; 
    private Duration sumOfEntries; 
    private List<TimeEntry> entries; 
} 

你如何用Java 8s的流做到這一點?

List<Day> days = timeEntries.stream().collect(Collectors.groupingBy(...? 

樣品輸入:

List<TimeEntry> timeEntries = [{"2016-01-22 10:00", "5 minutes"}, 
{"2016-01-23 08:00", "7 minutes"} , {"2016-01-23 08:43", "3 minutes"}] 

輸出示例:

[ 
{dayNr: 22, dayName: Friday, sumofEntries: 5 minutes 
    [{hourNr: 10, sumofEntries: 5 minutes}, 
    [{"2016-01-22 10:00", "5 minutes"}]} ]}, 
{dayNr: 23, dayName: Saturday, sumofEntries: 10 minutes 
    [{hourNr: 8, sumofEntries: 10 minutes}, 
    [{"2016-01-23 08:00", "7 minutes"}, 
    {"2016-01-23 08:43", "3 minutes"} ]} 
] 
+0

你不會得到一個'清單'除非你寫你的_own_收集器。你可以用核心庫做的最好的做法是在幾天內做一個'groupingBy',然後每天在幾個小時內爲'groupingBy'做一個'Map >>「。 –

回答

2

這是一個相當複雜的操作。直接的方法是首先創建一個Map<Integer, Map<Integer, List<TimeEntry>>>,然後在幾個小時內分組。一旦我們有了這張地圖,我們可以對其進行後處理,以創建想要的List<Day>

創建臨時圖很容易。我們可以使用Collectors.groupingBy(classifier, downstream)分類器返回日期(通過方法參考TimeEntry::getDay),下游收集器是另一個groupingBy收集器,它可以在幾個小時內分類(通過方法參考TimeEntry::getHour)。完成此步驟後,我們每天都會有一張地圖,其中值是映射到相應時間條目的每個小時的地圖。

接下來,我們需要做的是在該地圖中製作List<Day>。基本上,地圖的每個條目(因此每一天的編號)必須映射到相應的Day對象。

  • dayNr只是條目的關鍵。
  • dayName是當天和一小時的時間條目之一的日期名稱。一次輸入必須存在,因爲我們先將這些字段分組:爲了檢索它,我們得到當天數字的小時圖Map<Integer, List<TimeEntry>>,並保留第一個值。
  • hours可以通過操作當天數字的小時圖Map<Integer, List<TimeEntry>>來檢索。對於地圖的每個條目:
    • hourNr僅僅是入門
    • entries場的關鍵在入口
    • sumOfEntries的值添加時間條目的所有持續時間那個小時的號碼。
  • 最後,sumOfHours是當天數字的每個小時數的所有sumOfEntries的加法。

的完整實現如下,它假定所有的域對象有一個適當的構造和適當的getter方法:

public static void main(String[] args) { 
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); 
    List<TimeEntry> timeEntries = Arrays.asList(
     new TimeEntry(LocalDateTime.parse("2016-01-22 10:00", formatter), Duration.ofMinutes(5)), 
     new TimeEntry(LocalDateTime.parse("2016-01-23 08:00", formatter), Duration.ofMinutes(7)), 
     new TimeEntry(LocalDateTime.parse("2016-01-23 08:43", formatter), Duration.ofMinutes(3)) 
    ); 

    Map<Integer, Map<Integer, List<TimeEntry>>> map = 
     timeEntries.stream() 
        .collect(groupingBy(TimeEntry::getDay, groupingBy(TimeEntry::getHour))); 

    List<Day> output = 
     map.entrySet() 
      .stream() 
      .map(e -> { 
       String dayName = e.getValue().values().iterator().next().get(0).getDayName(); 
       List<Hour> hours = 
       e.getValue().entrySet() 
          .stream() 
          .map(he -> new Hour(he.getKey(), sumDuration(he.getValue(), TimeEntry::getDuration), he.getValue())) 
          .collect(toList()); 
       return new Day(e.getKey(), dayName, sumDuration(hours, Hour::getSumOfEntries), hours); 
      }) 
      .collect(toList()); 

    System.out.println(output); 
} 

private static <T> Duration sumDuration(List<T> list, Function<T, Duration> function) { 
    return list.stream().map(function::apply).reduce(Duration.ofMinutes(0), (d1, d2) -> d1.plus(d2)); 
} 

注意Duration對象從列表中加將其分解成幫助方法sumDuration

輸出上面的示例(假設方括號中一個toString()打印的所有字段):

[Day [dayNr=22, dayName=vendredi, sumOfHours=PT5M, hours=[Hour [hourNr=10, sumOfEntries=PT5M, entries=[TimeEntry [start=2016-01-22T10:00, duration=PT5M]]]]], Day [dayNr=23, dayName=samedi, sumOfHours=PT10M, hours=[Hour [hourNr=8, sumOfEntries=PT10M, entries=[TimeEntry [start=2016-01-23T08:00, duration=PT7M], TimeEntry [start=2016-01-23T08:43, duration=PT3M]]]]]] 
0

好了,我不能爲你寫的所有代碼,但是這將實現鮑里斯蜘蛛的想法和希望讓你開始。實例您TimeEntry列表,像這樣後(見how to initialize static ArrayList in one line):

List<TimeEntry> timeEntries = new ArrayList<TimeEntry>() { 
    { 
     add(new TimeEntry("2016-01-22T10:00", "PT5M")); 
     add(new TimeEntry("2016-01-23T08:00", "PT7M")); 
     add(new TimeEntry("2016-01-23T08:43", "PT3M")); 
    } 
}; 

那麼你stream它。您將需要兩個groupingBy收藏家。第一組將按小時分組:

Collector<TimeEntry, ?, Map<Integer, List<TimeEntry>>> groupingByHours 
    = Collectors.groupingBy(TimeEntry::getHour); 

第二組將按天分組。

Collector<TimeEntry, ?, Map<Integer, Map<Integer, List<TimeEntry>>>> groupingByDaysByHour 
    = Collectors.groupingBy(TimeEntry::getDay, groupingByHours); 

然後,您可以讓您的TimeEntry列表按天分組像當年小時這樣:

Map<Integer, Map<Integer, List<TimeEntry>>> dayMap = 
    timeEntries.stream() 
    .collect(groupingByDaysByHour); 

這會給輸出:

{22={10=[2016-01-22T10:00 PT5M]}, 23={8=[2016-01-23T08:00 PT7M, 2016-01-23T08:43 PT3M]}} 

爲了完整起見,你原來TimeEntry類修改爲與此示例一起工作:

class TimeEntry { 
    private LocalDateTime start; 
    private Duration duration; 

    public TimeEntry(String start, String duration) { 
     this.start = LocalDateTime.parse(start, DateTimeFormatter.ISO_LOCAL_DATE_TIME); 
     this.duration = Duration.parse(duration); 
    } 

    public int getDay() { 
     return start.get(ChronoField.DAY_OF_MONTH); 
    } 
    public int getHour() { 
     return start.get(ChronoField.HOUR_OF_DAY); 
    } 
    public long getDuration() { 
     return duration.toMinutes(); 
    } 
    @Override 
    public String toString() { 
     return start.toString() + " " + duration.toString(); 
    } 
}