it-swarm.dev

Seskupování položek podle údajů

Mám položky v mém seznamu a položky mají pole, které ukazuje datum vytvoření položky.

 enter image description here

a musím je seskupit na základě „komprese“, kterou uživatel dává. Možnosti jsou Day, Week, Month a Year.

Pokud uživatel vybere day kompresi, musím své položky seskupit tak, aby položky, které byly vytvořeny ve stejný den, byly seskupeny. Ve výše uvedeném příkladu jsou ve stejný den vytvořeny pouze položky 1 a 2. Ostatní jsou také skupiny, ale budou mít pouze jednu položku, protože v den jejich vytvoření je vytvořena pouze jedna položka.

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

Pokud uživatel vybere week:

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

Pokud uživatel vybere month:

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

Pokud uživatel vybere year:

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

Po vytvoření skupin není důležité datum položek. Myslím, že klíč může být cokoliv, dokud jsou skupiny vytvořeny.

V případě použití Map jsem si myslel, že následující klíče jsou následující:

day = den v roce
week = týden v roce
month = měsíc roku
year = rok

Jaké by bylo nejlepší řešení tohoto problému? Nemohl jsem to ani spustit a nemohu si představit jiné řešení než iteraci. 

7
drJava

Použil bych Collectors.groupingBy s upraveným LocalDate na klasifikátoru, takže položky s podobnými daty (podle komprese zadané uživatelem) jsou seskupeny dohromady.

Nejprve vytvořte následující 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());

Poznámka: pro "day" je používáno TemporalAdjuster, které umožňuje nedotčené datum.

Poté pomocí compression zadaného uživatelem dynamicky vyberte způsob seskupení seznamu položek:

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

LocalDate se nastavuje metodou LocalDate.with(TemporalAdjuster) .

Můžete popsat chování, které popisujete s toky 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()));

Dokumenty pro groupingBy a collect . Ve všech 4 případech LocalDate je použit jako klíč. Pro vhodné seskupení je upraven tak, že všechna data mají stejný měsíc a den nebo stejný den, ale jiný měsíc nebo stejný měsíc a stejný den v týdnu (pondělí), což vede ke zřejmým pravidlům seskupení. Datum v datech se nemění pouze klíč. To bude mít za to, že měsíc je stejný, když je i rok stejný a den je stejný, když je celé datum stejné.

Například při seskupování podle měsíce budou mít tato data stejný klíč:

01/01/2017 -> klíč 01/01/2017
04/01/2017 -> klíč 01/01/2017
05/01/2017 -> klíč 01/01/2017 

a při seskupování podle týdnů budou mít tato data stejný klíč (datum je předchozí pondělí):

04/01/2017 -> klíč 02/01/2017
05/01/2017 -> klíč 02/01/2017 

Možná budete chtít místo toho seskupit podle stejného dne v měsíci, například bez ohledu na rok a měsíc. Dosáhli byste to takto:

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

Která by se seskupila do 01.01.2017 s 01/10/2017 a poté 05/01/2017 s 05/04/2018

2
Manos Nikolaidis

Jediným detailem, který bych věnoval více pozornosti, je definice týdne. Předpokládám, že váš týden začíná v neděli a pokud musí být alespoň jeden den považován za první týden. (ISO 8601 uvádí, že týden začíná v pondělí a první týden musí být považován za nejméně 4 dny - pokud má méně dní, považuje se za nulový týden). Více podrobností o definicích týdnů můžete zkontrolovat v javadoc .

Pro definici tohoto týdne používám třídu Java.time.temporal.WeekFields. Používám metodu of, která používá první den v týdnu a minimální počet dnů v prvním týdnu (pokud používám verzi, která má Locale, výsledky se mohou lišit v závislosti na výchozím nastavení systému).

Také jsem vytvořil výčet pro typ komprese, ale to je volitelné:

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

Každopádně používám typ komprese, abych věděl, které pole bude sloužit pro seskupení dat. Pak jsem použil Collectors.groupingBy k seskupení:

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

Použijte HashMap takhle

 <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

Za předpokladu, že prvek položky má tyto atributy: 

private String item;
private LocalDate date;

Můžete to udělat takto: 

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

V závislosti na volbě uživatele by mapa vyústila v mapování položek podle jeho výběru


Podrobnosti:  

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

To bude iterovat nad položkou seznamu a podívat se na year of the date of the item, aby seskupila podle něj

1
azro