Grouping items by date

↘锁芯ラ 提交于 2019-11-27 16:23:38

问题


I have items in my list and the items have a field, which shows the creation date of the item.

and I need to group them based on a "compression", which the user gives. The options are Day, Week, Month and Year.

If the user selects day compression, I need to group my items as such that the items, which are created in the same day, will be groupped. In my example above, only item 1 and item 2 are created in the same day. The others are also groups but they will have only one item because at their day, only one item is created.

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

If the user selects week:

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

If the user selects month:

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

If the user selects year:

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

After groups are created, the date of the items are not important. I mean the key can be anything, as long as the groups are created.

In case of usage of Map, I thought as the keys as follow:

day = day of the year
week = week of the year
month = month of the year
year = year

What would be the best solution to this problem? I could not even start it an I cannot think of a solution other than iteration.


回答1:


I would use Collectors.groupingBy with an adjusted LocalDate on the classifier, so that items with similar dates (according to the compression given by the user) are grouped together.

For this, first create the following 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());

Note: for "day", a TemporalAdjuster that lets the date untouched is being used.

Next, use the compression given by the user to dynamically select how to group your list of items:

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

The LocalDate is adjusted by means of the LocalDate.with(TemporalAdjuster) method.




回答2:


You can get the behaviour you describe with java 8 streams:

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()));

Docs for groupingBy and collect. In all 4 cases LocalDate is used as key. To group appropriately, it is modified so that all dates have the same month and day or same day but different month or same month and same day of week (Monday) which leads to obvious grouping rules. The date in your data is not modified only the key. This will consider that the month is the same when also the year is the same and the day is the same when the full date is the same.

For example when grouping by month these dates will have the same key:

01/01/2017 --> key 01/01/2017
04/01/2017 --> key 01/01/2017
05/01/2017 --> key 01/01/2017

and when grouping by week these dates will have the same key (date is previous monday):

04/01/2017 --> key 02/01/2017
05/01/2017 --> key 02/01/2017

You may want instead to group by same day of month for example regardless of year and month. You would achieve it like this:

Map<Integer, List<Data>> byDayOfMonth = data.stream()
        .collect(groupingBy(d -> d.getDate().getDayOfMonth()));

Which would group together 01/01/2017 with 01/10/2017 and then 05/01/2017 with 05/04/2018




回答3:


Use a HashMap like this

 <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:


The only detail I'd pay more attention is the week definition. I'm assuming that your week starts at Sunday and if it must have at least a minimum of 1 day to be considered the first week. (ISO 8601 states that a week starts at Monday and it must have at least 4 days to be considered the first week - if it has fewer days, it's considered week zero). You can check the javadoc for more details about week definitions.

To get this week definition, I'm using java.time.temporal.WeekFields class. I'm using the of method that explicits uses the first day of week and the minimum number of days in the first week (if I use the version that takes a Locale, results might differ depending on the system's default locale).

I also created an enum for the compression type, but that's optional:

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

Anyway, I use the compression type to know which field will be used to group the dates. Then I used Collectors.groupingBy to do the grouping:

// 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)));
}



回答5:


Assuming your item element have these attributes :

private String item;
private LocalDate date;

You can do like this :

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;
}

Depending on the user choice, the map would result as mapping the items following his choice


Details :

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

This will iterate over the item of the list, and look at the year of the date of the item to group them by it



来源:https://stackoverflow.com/questions/45060886/grouping-items-by-date

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!