Разделить элементы по условиям

Рейтинг: 0Ответов: 2Опубликовано: 22.03.2023

Есть класс Item, описывающий записи в БД.

Также есть коллекция List<Item>, хранящая список таки объектов. Записи группируются по нескольким полям:

record MyKey(long id, String city, OrderStatus status) {
    public MyKey(Item item) {
        this(item.getId(), item.getCity(), item.getOrderStatus());
    }
}


public static Map<MyKey, List<Item>> groupBy(List<Item> list) {
    Map<MyKey, List<Item>> map = new LinkedHashMap<>();
    list.forEach(item -> map
        .computeIfAbsent(new MyKey(item), k -> new ArrayList<>())
        .add(item)
    );

    // отладочный вывод
    map.forEach((key, val) -> {
        System.out.println(key);
        val.forEach(item -> System.out.println("\t" + item));
    });

    return map;
}

Добавилось поле date, которая хранит дату заказа. Как сгруппировать элементы так, чтобы в одной группе были заказы, у которых разница минимальной и максимальной даты не превышали 10 дней?

Ответы

▲ 0

Напишу код группировки на C#, на java сами переведете.

Допустим, есть класс

class Item
{
    public DateTime Date { get; set; }
}

и есть сортированный по дате массив

var items = new[] {
    new Item(){Date = DateTime.Now.Date },
    new Item(){Date = DateTime.Now.Date.AddDays(7) },
    new Item(){Date = DateTime.Now.Date.AddDays(14) },
    new Item(){Date = DateTime.Now.Date.AddDays(21) },
    new Item(){Date = DateTime.Now.Date.AddDays(28) },
    new Item(){Date = DateTime.Now.Date.AddDays(35) },
    new Item(){Date = DateTime.Now.Date.AddDays(42) },
};

Тогда алгоритм сводится к тому, чтобы пройти по массиву и закинуть группы в список списков

var dates = new List<List<Item>>(); 
int ind = 0;
while(ind < items.Length)
{
    var list = new List<Item>();
    list.Add(items[ind]);
    ind++;
    
    while(ind < items.Length && (items[ind].Date - list[0].Date).TotalDays <10)
    {
        list.Add(items[ind]);
        ind++;
    }
    
    dates.Add(list);
}

Вывод на печать

foreach(var group in dates)
{
    foreach(var item in group)
    {           
        Console.WriteLine(item.Date.ToString("dd.MM.yyyy"));
    }
    
    Console.WriteLine("--------------");
}

Вывод в консоли

22.03.2023
29.03.2023
--------------
05.04.2023
12.04.2023
--------------
19.04.2023
26.04.2023
--------------
03.05.2023
--------------
▲ 0
  1. Ищете заказ с минимальной датой, при помощи того же Stream API Stream::min(Comparator<T> cmp)
  2. Проходите по списку и разбиваете его на две части, например при помощи уже упоминавшегося ранее Collectors::partitioningBy
private static Map<Boolean, List<Item>> byDate(List<Item> items, int days) {
    LocalDate minDate = items.stream()
        .map(Item::getDate)              // Stream<LocalDate>
        .min(Comparator.naturalOrder())  // Optional<LocalDate>
        .get();

    return items.stream()
        .collect(Collectors.partitioningBy(
            item -> ChronoUnit.DAYS.between(minDate, item.getDate()) < days
        ));
}

Разбиение по количеству дней прошедших от начальной даты:

private static Map<Integer, List<Item>> byDate(List<Item> items, int days) {
    LocalDate minDate = items.stream()
        .map(Item::getDate)              // Stream<LocalDate>
        .min(Comparator.naturalOrder())  // Optional<LocalDate>
        .get();

    return items.stream()
        .collect(Collectors.groupingBy(
            item -> (int) ChronoUnit.DAYS.between(minDate, item.getDate()) / days
        ));
}
List<Item> items = Arrays.asList(
    new Item(1, LocalDate.of(2023, 3, 1)),
    new Item(2, LocalDate.of(2023, 3, 14)),
    new Item(3, LocalDate.of(2023, 3, 19)),
    new Item(4, LocalDate.of(2023, 3, 30))
);

var groups = byDate(items, 10);
groups.forEach((k, v) -> System.out.println(k + ": " + v));
0: [Item[id=1, date=2023-03-01]]
1: [Item[id=2, date=2023-03-14], Item[id=3, date=2023-03-19]]
2: [Item[id=4, date=2023-03-30]]