【问题标题】:Filter Multimap By Key Based on Date Range根据日期范围按键过滤多图
【发布时间】:2017-07-31 17:37:05
【问题描述】:

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

由于每个日期可能有多次付款,我使用的是 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"));

根据日期范围过滤此地图的推荐方法是什么?

例如,显示 2017 年 3 月 2 日至 2017 年 3 月 31 日之间的条目。

【问题讨论】:

    标签: java dictionary guava multimap


    【解决方案1】:

    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 的实现。

    请注意 JavaDocs 中针对filterKeys 方法返回的Multimap 实例的一些细节,例如:

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

    ...

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

    ...

    返回的多映射不是线程安全的或可序列化的,即使未过滤是。

    ...

    许多过滤后的 multimap 的方法,例如 size(),遍历底层 multimap 中的每个键/值映射,并确定哪些满足过滤器。当不需要实时取景时,复制过滤后的多图并使用副本可能会更快。

    作为补充说明,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);
            }
        };
    }
    

    【讨论】:

      【解决方案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()) {
          ...
      }
      

      【讨论】:

      • FWIW 在这种情况下,两个地图视图都是NavigableMaps
      • @Xaerxess 好的,谢谢。关于... some code vs // ... some code:我是故意的。这两个版本都没有按原样做任何有用的事情。我的甚至没有编译,但这是一件好事(你可能会错过评论,但不能错过编译错误)。 YMMV。
      • 很公平,我还原了那些 cmets。有趣的事实:Perl5 has ... operator which dies with "Unimplemented" message.
      猜你喜欢
      • 2016-11-25
      • 2020-02-15
      • 1970-01-01
      • 2015-05-12
      • 1970-01-01
      • 2021-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多