web-dev-qa-db-fra.com

Regroupement des articles par date

J'ai des articles dans ma liste et les articles ont un champ qui indique la date de création de l'article.

 enter image description here

et je dois les regrouper sur la base d'une "compression", que l'utilisateur donne. Les options sont Day, Week, Month et Year.

Si l'utilisateur sélectionne la compression day, je dois regrouper mes éléments afin que les éléments créés le même jour soient groupés. Dans mon exemple ci-dessus, seuls les éléments 1 et 2 sont créés le même jour. Les autres sont aussi des groupes mais ils n’auront qu’un seul élément car, à leur journée, un seul élément est créé.

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

Si l'utilisateur sélectionne week:

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

Si l'utilisateur sélectionne month:

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

Si l'utilisateur sélectionne year:

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

Une fois les groupes créés, la date des éléments n'a pas d'importance. Je veux dire que la clé peut être n'importe quoi, tant que les groupes sont créés.

En cas d'utilisation de Map, j'ai pensé comme suit:

day = jour de l'année
week = semaine de l'année
month = mois de l'année
year = année

Quelle serait la meilleure solution à ce problème? Je ne pouvais même pas le démarrer et je ne pouvais pas penser à une solution autre que l'itération. 

7
drJava

J'utiliserais Collectors.groupingBy avec une LocalDate ajustée sur le classificateur, de sorte que les éléments ayant des dates similaires (selon le compression donné par l'utilisateur) soient regroupés.

Pour cela, commencez par créer la Map suivante:

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

Remarque: pour "day", une TemporalAdjuster qui laisse la date intacte est utilisée.

Ensuite, utilisez la compression donnée par l’utilisateur pour sélectionner dynamiquement comment grouper votre liste d’éléments:

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

La LocalDate est ajustée à l’aide de la méthode LocalDate.with(TemporalAdjuster) .

Vous pouvez obtenir le comportement que vous décrivez avec les flux Java 8:

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 . Dans tous les 4 cas LocalDate est utilisé comme clé. Pour grouper de manière appropriée, il est modifié afin que toutes les dates aient le même mois et le même jour ou le même jour mais un mois ou un même mois différent et le même jour de la semaine (lundi), ce qui conduit à des règles de regroupement évidentes. La date dans vos données n'est pas modifiée que la clé. Ceci considérera que le mois est le même quand aussi l'année est la même et le jour est le même quand la date complète est la même.

Par exemple, lors du regroupement par mois, ces dates auront la même clé:

01/01/2017 -> clé 01/01/2017
01/04/2017 -> clé 01/01/2017
01/05/2017 -> clé 01/01/2017 

et lors du regroupement par semaine, ces dates auront la même clé (la date est le lundi précédent):

04/01/2017 -> clé 02/01/2017
05/01/2017 -> clé 02/01/2017 

Vous souhaiterez peut-être plutôt grouper par le même jour du mois, par exemple, indépendamment de l'année et du mois. Vous y parviendrez comme ceci:

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

Qui regrouperait le 01/01/2017 au 01/10/2017 et ensuite le 05/01/2017 au 05/04/2018

2
Manos Nikolaidis

Le seul détail auquel je ferais plus attention est la définition de la semaine. Je suppose que votre semaine commence le dimanche et qu’elle doit avoir au moins un jour pour être considérée comme la première semaine. (ISO 8601 indique qu'une semaine commence le lundi et qu'elle doit disposer d'au moins 4 jours pour être considérée comme la première semaine - si elle compte moins de jours, elle est considérée comme une semaine zéro). Vous pouvez consulter le fichier javadoc pour plus de détails sur les définitions de semaine.

Pour obtenir cette définition de la semaine, j'utilise la classe Java.time.temporal.WeekFields. J'utilise la méthode of qui explique le premier jour de la semaine et le nombre minimum de jours de la première semaine (si j'utilise la version prenant une Locale, les résultats peuvent varier en fonction des paramètres régionaux par défaut du système).

J'ai également créé une énumération pour le type de compression, mais c'est facultatif:

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

Quoi qu'il en soit, j'utilise le type de compression pour savoir quel champ sera utilisé pour regrouper les dates. Ensuite, j'ai utilisé Collectors.groupingBy pour faire le regroupement:

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

Utilisez un HashMap comme celui-ci

 <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>());
1
Joey Pinto

En supposant que votre élément ait les attributs suivants: 

private String item;
private LocalDate date;

Vous pouvez faire comme ça: 

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

En fonction du choix de l'utilisateur, la carte aura pour résultat de mapper les éléments suivant son choix.


Détails :  

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

Cela va parcourir l'élément de la liste et regarder le year of the date of the item pour les regrouper

1
azro