Фильтр Multimap по ключу на основе диапазона дат
У меня есть набор данных, содержащий платежные транзакции, состоящие из дат и сумм. Я храню их в структуре данных карты с датой в качестве ключа и суммы в качестве значения.
Поскольку на дату может быть несколько платежей, я использую Multimap из библиотеки Google Guava.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy");
Multimap<LocalDate,BigDecimal> payments = ArrayListMultimap.create();
payments.put(LocalDate.parse("12/25/2016",formatter), new BigDecimal("1000"));
payments.put(LocalDate.parse("01/15/2017",formatter), new BigDecimal("250"));
payments.put(LocalDate.parse("01/25/2017",formatter), new BigDecimal("500"));
payments.put(LocalDate.parse("03/20/2017",formatter), new BigDecimal("500"));
payments.put(LocalDate.parse("04/15/2017",formatter), new BigDecimal("1000"));
payments.put(LocalDate.parse("06/15/2017",formatter), new BigDecimal("1000"));
Каков рекомендуемый подход к фильтрации этой карты на основе диапазона дат?
Например, показать записи между 3/2/2017-3/31/2017.
2 ответа
Multimaps#filterKeys
метод позволяет фильтровать существующий Multimap
по ключам, соответствующим любому Predicate
что вы код Возвращаемое значение Multimap
который содержит только записи, которые удовлетворяют предикату фильтрации.
Во-первых, давайте определим вспомогательный метод, который может создать Predicate
для проверки того, что даты находятся между указанным диапазоном.
private static Predicate<LocalDate> between(final LocalDate begin, final LocalDate end) {
return new Predicate<LocalDate>() {
@Override
public boolean apply(LocalDate date) {
return (date.compareTo(begin) >= 0 && date.compareTo(end) <= 0);
}
};
}
После этого вы можете использовать предикат для фильтрации желаемого диапазона.
void testFilterPayments() {
Multimap<LocalDate, BigDecimal> payments = ArrayListMultimap.create();
payments.put(LocalDate.parse("2016-12-25"), new BigDecimal("1000"));
payments.put(LocalDate.parse("2017-01-15"), new BigDecimal("250"));
payments.put(LocalDate.parse("2017-01-15"), new BigDecimal("1250"));
payments.put(LocalDate.parse("2017-01-15"), new BigDecimal("2250"));
payments.put(LocalDate.parse("2017-01-25"), new BigDecimal("500"));
payments.put(LocalDate.parse("2017-03-20"), new BigDecimal("500"));
payments.put(LocalDate.parse("2017-04-15"), new BigDecimal("1000"));
payments.put(LocalDate.parse("2017-06-15"), new BigDecimal("1000"));
System.out.println(Multimaps.filterKeys(payments,
between(LocalDate.parse("2017-01-01"), LocalDate.parse("2017-04-01"))));
// Output:
// {2017-01-25=[500], 2017-03-20=[500], 2017-01-15=[250, 1250, 2250]}
System.out.println(Multimaps.filterKeys(payments,
between(LocalDate.parse("2017-01-01"), LocalDate.parse("2017-01-15"))));
// Output:
// {2017-01-15=[250, 1250, 2250]}
System.out.println(Multimaps.filterKeys(payments,
between(LocalDate.parse("2016-01-01"), LocalDate.parse("2017-12-31"))));
// Output:
// {2017-06-15=[1000], 2017-01-25=[500], 2017-03-20=[500], 2016-12-25=[1000], 2017-01-15=[250, 1250, 2250], 2017-04-15=[1000]}
System.out.println(Multimaps.filterKeys(payments,
between(LocalDate.parse("2001-01-01"), LocalDate.parse("2015-12-31"))));
// Output:
// {}
}
Я упростил этот пример, чтобы использовать формат даты по умолчанию для анализа. Похоже, ваш оригинальный образец использует пользовательский formatter
, но все те же методы применяются к фильтрации по предикату.
Моя реализация предиката соответствует инклюзивному диапазону для обоих begin
а также end
, Если у вас были немного другие требования (например, эксклюзивный ассортимент, такой, что end
не включены в результаты), то вы можете настроить реализацию apply
соответственно.
Пожалуйста, обратите внимание на некоторые детали, указанные в JavaDocs для Multimap
экземпляр возвращен filterKeys
метод, такой как:
Возвращенная мультикарта представляет собой живое представление нефильтрованного; изменения одного влияют на другое.
...
Полученные мультикарты имеют итераторы, которые не поддерживают remove ()...
...
Возвращенная мультикарта не является поточно-ориентированной или сериализуемой, даже если она нефильтрована.
...
Многие из методов отфильтрованной мультикарты, такие как size(), выполняют итерацию для каждого сопоставления ключ / значение в базовой мультикарте и определяют, какие из них удовлетворяют фильтру. Когда просмотр в реальном времени не требуется, может быть быстрее скопировать отфильтрованную мультикарту и использовать копию.
Как добавленное примечание, between
Предикат можно сделать более гибким, изменив сигнатуру метода для использования обобщений, которые принимают различные Comparable
типы, а не только LocalDate
,
private static <T extends Comparable<? super T>> Predicate<T> between(final T begin, final T end) {
return new Predicate<T>() {
@Override
public boolean apply(T value) {
return (value.compareTo(begin) >= 0 && value.compareTo(end) <= 0);
}
};
}
Вам нужно Multimap
чтобы быть отсортированным, в противном случае вы должны повторить все это. К счастью, есть такая мультикарта:
ListMultimap<LocalDate, BigDecimal> multimap =
MultimapBuilder.treeKeys().arrayListValues().build();
... fill
// This cast is safe.
SortedMap<LocalDate, Collection<BigDecimal>> asMap =
(SortedMap<LocalDate, Collection<BigDecimal>>) multimap.asMap();
SortedMap<LocalDate, Collection<BigDecimal>> subMap =
asMap.subMap(from, to);
for (Map.Entry<LocalDate, Collection<BigDecimal>> e : subMap.entrySet()) {
...
}