Java 8 Stream Map Reduce

时间:2015-08-12 17:47:55

标签: java dictionary java-8 java-stream reduce

我在java 8流上全新,我试图获得下面描述的行为:

class myVO {
    Long id;
    BigDecimal value;
    Date date;
    getter/setter
}

myVO method(Map<Long, myVO> inputMap) {
    return inputMap.stream()
                   .filter(x -> x.getValue().compareTo(BigDecimal.ZERO) > 0)
                   .sorted(); //FIXME
}

我想只获得一个myVO obj,它是具有相同日期(最低值)的记录的BigDecimal值的和。

e.g。

xL, 10, 2015/07/07
xL, 15, 2015/07/08
xL, 20, 2015/07/07
xL, 25, 2015/07/09

结果

xL, 30, 2015/07/07

N.B。 id(xL)不是重要的字段。

更新 - 采用的解决方案(虽然不是单程)

if(null != map && !map.isEmpty()) {
        Date closestDate = map.values().stream()
                .filter(t -> t.getDate() != null)
                .map(MyVO::getDate)
                .min(Comparator.naturalOrder()).orElse(null);
        myVO.setDate(closestDate);

        BigDecimal totalValue = map.values().stream()
                .filter(x -> x.getValue() != null && x.getValue().signum() != 0)
                .filter(t -> t.getDate().equals(closestDate))
                .map(MyVO::getValue)
                .reduce(BigDecimal::add).orElse(null);
        myVO.setValue(totalValue != null ? totalValue.setScale(2, BigDecimal.ROUND_HALF_DOWN) : totalValue);
    }

3 个答案:

答案 0 :(得分:1)

考虑到inputMap至少有一个条目,可以这样做:

myVO method(Map<Long, myVO> inputMap) {
    Date minDate = inputMap.values().stream().map(myVO::getDate).min(Comparator.naturalOrder()).get();

    BigDecimal sum = inputMap.values().stream().filter(t -> t.getDate().equals(minDate)).map(myVO::getValue).reduce(BigDecimal::add).get();

    myVO myVOObj = new myVO();
    myVOObj.setDate(minDate);
    myVOObj.setValue(sum);
    myVOObj.setId(??);

    return myVOObj;
}

答案 1 :(得分:1)

我写了一个自定义收藏家来解决这些任务。它位于我的StreamEx库中,名为MoreCollectors.maxAll(downstream)。使用它,你可以在一些准备工作后单程解决任务。

首先,您的compareTo方法是错误的。它永远不会返回0并且实际上它违反了合同(a.compareTo(a) == -1违反了反身性和反对称属性)。它可以像这样轻松修复:

@Override
public int compareTo(myVO o) {
    return o.getDate().compareTo(this.getDate());
}

接下来,让我们添加一个myVO.merge()方法,它可以根据您的要求合并两个myVO对象:

public myVO merge(myVO other) {
    myVO result = new myVO();
    result.setId(getId());
    result.setDate(getDate());
    result.setValue(getValue().add(other.getValue()));
    return result;
}

现在结果可以这样找到:

Optional<myVO> result = input.stream().filter(x -> x.getValue().signum() > 0)
    .collect(MoreCollectors.maxAll(Collectors.reducing(myVO::merge)));

如果输入列表为空,则结果Optional将为空。

如果您不喜欢依赖第三方库,您可以查看此收藏家的source code并在项目中写下类似内容。

答案 2 :(得分:1)

如果你想一想,减少并不复杂。您必须指定一个减少函数:

  • 如果两个元素的日期不同,则返回日期较短的对象
  • 如果匹配,则创建包含值总和的结果对象

与两步法相比,它可以执行更多add次操作,当流中出现较低日期时,其结果将被删除,另一方面,执行日期比较的次数将为一半。

MyVO method(Map<Long, MyVO> inputMap) {
    return inputMap.values().stream()
        .reduce((a,b)->{
            int cmp=a.getDate().compareTo(b.getDate());
            if(cmp==0)
            {
                MyVO r=new MyVO();
                r.setDate(a.date);
                r.setValue(a.value.add(b.value));
                return r;
            }
            return cmp<0? a: b;
        }).orElse(null);
}

它看起来不简洁的主要原因是它必须创建一个新的MyVO实例,在匹配日期的情况下保存总和,因为缩减函数不能修改值对象。而且您没有指定存在哪些构造函数。如果有一个合适的构造函数接收DateBigDecimal,则该函数几乎可以是一行的。

请注意,如果只有一个具有最低日期的对象,此方法将返回原始MyVO对象。

或者,您可以使用可变缩减,始终创建一个新的MyVO实例来保存结果,但只为每个线程创建一个实例并在缩减期间修改该新实例:

MyVO method(Map<Long, MyVO> inputMap) {
    BiConsumer<MyVO, MyVO> c=(a,b)->{
        Date date = a.getDate();
        int cmp=date==null? 1: date.compareTo(b.getDate());
        if(cmp==0) a.setValue(a.getValue().add(b.getValue()));
        else if(cmp>0)
        {
            a.setValue(b.getValue());
            a.setDate(b.getDate());
        }
    };
    return inputMap.values().stream().collect(()->{
        MyVO r = new MyVO();
        r.setValue(BigDecimal.ZERO);
        return r;
    }, c, c);
}

此处,如果存在适当的构造函数(或者如果初始值保证为非Supplier null),则BigDecimal.ZERO可以是一行...