按日期对项目进行分组

时间:2017-07-12 14:43:42

标签: java java-8 java-stream java-time

我的列表中有项目,项目有一个字段,显示项目的创建日期。

enter image description here

我需要根据用户提供的" compression"对它们进行分组。选项包括DayWeekMonthYear

如果用户选择day压缩,我需要将我的项目分组,以便在同一天创建的项目将被分组。在上面的示例中,仅在同一天创建了第1项和第2项。其他人也是团体,但他们只有一个项目,因为当天只创建了一个项目。

{{item1, item2}, {item3}, {item4}, {item5}, {item6}, {item7}}

如果用户选择week

{{item1, item2, item3, item4}, {item5}, {item6}, {item7}}

如果用户选择month

{{item1, item2, item3, item4, item5}, {item6}, {item7}}

如果用户选择year

{{item1, item2, item3, item4, item5, item6}, {item7}}

创建组后,项目的日期并不重要。我的意思是,只要创建了组,密钥就可以是任何东西。

如果使用Map,我认为按键如下:

day =一年中的某一天 week =一年中的一周
month =一年中的某个月
year =年

这个问题的最佳解决方案是什么?我甚至无法启动它,除了迭代之外我无法想到解决方案。

5 个答案:

答案 0 :(得分:22)

我会在分类器上使用Collectors.groupingBy和调整后的LocalDate,以便将具有相似日期的项目(根据用户提供的压缩)组合在一起。

为此,首先创建以下Map

static final Map<String, TemporalAdjuster> ADJUSTERS = new HashMap<>();

ADJUSTERS.put("day", TemporalAdjusters.ofDateAdjuster(d -> d)); // identity
ADJUSTERS.put("week", TemporalAdjusters.previousOrSame(DayOfWeek.of(1)));
ADJUSTERS.put("month", TemporalAdjusters.firstDayOfMonth());
ADJUSTERS.put("year", TemporalAdjusters.firstDayOfYear());

注意:对于"day",正在使用TemporalAdjuster来保持日期不变。

接下来,使用用户提供的compression动态选择如何对项目列表进行分组:

Map<LocalDate, List<Item>> result = list.stream()
    .collect(Collectors.groupingBy(item -> item.getCreationDate()
            .with(ADJUSTERS.get(compression))));

LocalDate通过LocalDate.with(TemporalAdjuster)方法进行调整。

答案 1 :(得分:3)

您可以获得使用java 8流描述的行为:

plot(hclust(as.sim(""dissimilarityMATRIX""), "average"))

groupingBycollect的文档。在所有4个案例中LocalDate被用作关键。要进行适当分组,需要对其进行修改,以使所有日期具有相同的月份和日期或同一天但不同的月份或同一个月以及同一天(星期一),从而产生明显的分组规则。数据中的日期不会仅修改密钥。这将考虑月份是相同的,当年份相同时,并且当完整日期相同时,日期是相同的。

例如,按月分组时,这些日期将具有相同的密钥:

  

01/01/2017 - &gt;关键01/01/2017
  04/01/2017 - &gt;关键01/01/2017
  2017/01/05 - &gt;关键01/01/2017

当按周分组时,这些日期将具有相同的密钥(日期是上一个星期一):

  

04/01/2017 - &gt;关键02/01/2017
  2017/01/05 - &gt;关键02/01/2017

您可能希望在每月的同一天进行分组,例如,无论年份和月份。你可以这样做:

Map<LocalDate, List<Data>> byYear = data.stream()
        .collect(groupingBy(d -> d.getDate().withMonth(1).withDayOfMonth(1)));
Map<LocalDate, List<Data>> byMonth = data.stream()
        .collect(groupingBy(d -> d.getDate().withDayOfMonth(1)));
Map<LocalDate, List<Data>> byWeek = data.stream()
        .collect(groupingBy(d -> d.getDate().with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))));
Map<LocalDate, List<Data>> byDay = data.stream()
        .collect(groupingBy(d -> d.getDate()));

2017年10月1日至2017年5月1日05/01/20与

组合在一起

答案 2 :(得分:2)

我唯一需要注意的细节是周定义。我假设你的一周从星期日开始,如果它必须至少有一天至少被认为是第一周。 (ISO 8601规定,一周从星期一开始,必须至少有4天才能被视为第一周 - 如果它的天数较少,则认为是零周)。您可以查看javadoc以获取有关周定义的更多详细信息。

要获得本周定义,我正在使用java.time.temporal.WeekFields课程。我使用的是of方法,explicits使用的是第一周的第一天和第一周的最小天数(如果我使用的版本为Locale,结果可能会有所不同,具体取决于系统的默认语言环境)。

我还为压缩类型创建了一个枚举,但这是可选的:

enum CompressionType {
    DAY, WEEK, MONTH, YEAR;
}

无论如何,我使用压缩类型来知道将使用哪个字段对日期进行分组。然后我使用Collectors.groupingBy进行分组:

// assuming you have a list of dates
List<LocalDate> dates = new ArrayList<>();
dates.add(LocalDate.of(2017, 1, 1));
dates.add(LocalDate.of(2017, 1, 1));
dates.add(LocalDate.of(2017, 1, 4));
dates.add(LocalDate.of(2017, 1, 5));
dates.add(LocalDate.of(2017, 1, 29));
dates.add(LocalDate.of(2017, 10, 1));
dates.add(LocalDate.of(2018, 4, 5));

CompressionType compression = // get CompressionType from user input
final TemporalField groupField;
switch (compression) {
    case DAY:
        groupField = ChronoField.DAY_OF_YEAR;
        break;
    case WEEK:
    // week starts at Sunday, minimum of 1 day in the first week
        groupField = WeekFields.of(DayOfWeek.SUNDAY, 1).weekOfWeekBasedYear();
        break;
    case MONTH:
        groupField = ChronoField.MONTH_OF_YEAR;
        break;
    case YEAR:
        groupField = ChronoField.YEAR;
        break;
    default:
        groupField = null;
}
if (groupField != null) {
    Map<Integer, List<LocalDate>> result = dates.stream().collect(Collectors.groupingBy(d -> d.get(groupField)));
}

答案 3 :(得分:1)

使用像这样的HashMap

 <Integer,ArrayList<Date>>

1. set filter=DAY/MONTH/YEAR

2.Iterate the date_obj

3.Use Calendar package to get a variable val=Calendar.get(filter)

4. If hashmap.keyset().contains(val)
      hashmap.get(val).add(date_obj.date)
   Else
      hashmap.put(val,new ArrayList<date_obj>());

答案 4 :(得分:1)

假设您的item元素具有以下属性:

private String item;
private LocalDate date;

你可以这样做:

ArrayList<Element> list = new ArrayList<>();
list.add(new Element("item 1", LocalDate.of(2017, 01, 01)));
list.add(new Element("item 2", LocalDate.of(2017, 01, 01)));

WeekFields weekFields = WeekFields.of(Locale.getDefault());
String userChoice = new Scanner(System.in).nextLine();
Map<Integer, List<Element>> map;

switch (userChoice) {
     case "day":
          map = list.stream().collect(Collectors.groupingBy(element 
                              -> element.getDate().getDayOfMonth()));
          break;
     case "week":
          map = list.stream().collect(Collectors.groupingBy(element 
                              -> element.getDate().get(weekFields.weekOfWeekBasedYear())));
          break;
     case "month":
          map = list.stream().collect(Collectors.groupingBy(element 
                              -> element.getDate().getMonthValue()));
          break;
     case "year":
          map = list.stream().collect(Collectors.groupingBy(element 
                              -> element.getDate().getYear()));
          break;
     default:
          break;
}

根据用户的选择,地图会导致按照他的选择映射项目

详细信息:

map = list.stream().collect(Collectors.groupingBy(element 
                                        -> element.getDate().getYear()));

这将遍历列表中的项目,并查看year of the date of the item以按其分组