根据日期范围按键过滤多图

时间:2017-07-31 17:37:05

标签: java dictionary guava multimap

我有一个包含日期和金额的付款交易的数据集。我将这些存储在地图数据结构中,其中日期为关键,数量为值。

由于每个日期可能有多笔付款,我使用Google Guava库中的Multimap。

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 个答案:

答案 0 :(得分:3)

Multimaps#filterKeys方法允许您通过与您编码的任意Predicate匹配的键过滤现有Multimap。它的返回值是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,但所有相同的技术都适用于谓词过滤。

我的谓词实现与beginend的包含范围匹配。如果您的要求略有不同(例如排名范围,结果中不包含end),那么您可以相应地调整apply的实施。

请注意Multimap方法返回的filterKeys实例的JavaDocs中提到的一些详细信息,例如:

  

返回的多图是未过滤的实时视图;改变为一个影响另一个。

     

...

     

生成的多地图视图的迭代器不支持remove()...

     

...

     

返回的多图不是线程安全的或可序列化的,即使未经过滤也是如此。

     

...

     

许多过滤后的multimap的方法(例如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);
        }
    };
}

答案 1 :(得分:2)

您需要对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()) {
    ...
}