Java:使用lambda在流中查找多个最小/最大属性值

时间:2017-02-03 10:45:11

标签: java lambda java-stream

我正在寻找一种简洁的方法来查找一组属性值,这些属性值在给定的对象流中是最小的或最大的。

例如:

c->sortedBy(p | p.surname + '_' + p.name)

这是非常多余的,因为我必须为我需要的每个最小/最大属性创建一个新流。

在我的用例中,类似的方法是指数成本的递归算法的一部分,因此有一个并发解决方案,只打开一次流将是伟大的。更好的是一个解决方案,它可以在没有终止的现有流上工作(但我怀疑这是可能的)。

你知道如何改进吗?

编辑:我忘了提及class Dimensions { final int startX, startY, endX, endY; //Set by constructor } /** * For the given dimensions, looks where the dimensions intersect. * These coordinates define the sub-array, which is applied to the given function. * * @return the value returned by applying the sub-array in the given dimensions to the given function */ <S, T> T performOnIntersections(Function<S, T> function, S[][] inputArray, Dimensions...dimensions){ int maxStartX = Arrays.stream(dimensions).max(Comparator.comparingInt(d -> d.startX)).get().startX; int maxStartY = Arrays.stream(dimensions).max(Comparator.comparingInt(d -> d.startY)).get().startY; int minEndX = Arrays.stream(dimensions).min(Comparator.comparingInt(d -> d.endX)).get().endX; int minEndY = Arrays.stream(dimensions).min(Comparator.comparingInt(d -> d.endY)).get().endY; return applyInBetween(inputArray, function, maxStartX, maxStartY, minEndX, minEndY); } 是不可变的,这在使用Dimension时是相关的。

编辑2:使用lambda表达式调用流上的Supplier而不是创建collect()实例具有最佳的运行时性能。 jessepeng首先提到它,所以我将他的帖子标记为解决方案。我的实施现在是:

DimensionsMinMaxCollector

3 个答案:

答案 0 :(得分:2)

您可以使用collect()将流的所有元素合并为一个包含所需值的Dimensions对象。

来自Stream文档:

<R> R collect(Supplier<R> supplier,
             BiConsumer<R, ? super T> accumulator,
             BiConsumer<R, R> combiner);
     

对此流的元素执行可变减少操作。   可变减少是减少值是可变的减少   结果容器,例如ArrayList,并且元素被合并   通过更新结果的状态而不是替换   结果。这产生的结果相当于:

 R result = supplier.get();
 for (T element : this stream)
     accumulator.accept(result, element);
 return result;

因此,在您的情况下,您需要一个创建新Dimension对象的供应商,累加器和组合器将进行比较和设置值。

Dimensions searchDimensions = Arrays.stream(dimensions).collect(Dimensions::new, (dimension, dimension2) -> {
            dimension.endX = dimension.endX < dimension2.endX ? dimension.endX : dimension2.endX;
            dimension.endY = dimension.endY < dimension2.endY ? dimension.endY : dimension2.endY;
            dimension.startX = dimension.startX > dimension2.startX ? dimension.startX : dimension2.startX;
            dimension.startY = dimension.startY > dimension2.startY ? dimension.startY : dimension2.startY;
        }, (dimension, dimension2) -> {
            dimension.endX = dimension.endX < dimension2.endX ? dimension.endX : dimension2.endX;
            dimension.endY = dimension.endY < dimension2.endY ? dimension.endY : dimension2.endY;
            dimension.startX = dimension.startX > dimension2.startX ? dimension.startX : dimension2.startX;
            dimension.startY = dimension.startY > dimension2.startY ? dimension.startY : dimension2.startY;
        });

return applyInBetween(inputArray, function, searchDimensions.startX, searchDimensions.startY, searchDimensions.endX, searchDimensions.endY);

修改 由于Dimensions是不可变的,因此不适合执行可变减少操作。可以使用一个简单的数组来存储这四个值。

<S, T> T performOnIntersections(Function<S, T> function, S[][] inputArray, Dimensions...dimensions){

    Supplier<int[]> supplier = () -> new int[]{Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE};
    BiConsumer<int[], Dimensions> accumulator = (array, dim) -> {
        array[0] = dim.startX > array[0] ? dim.startX : array[0];
        array[1] = dim.startY > array[1] ? dim.startY : array[1];
        array[2] = dim.endX < array[2] ? dim.endX : array[2];
        array[3] = dim.endY < array[3] ? dim.endY : array[3];
    };
    BiConsumer<int[], int[]> combiner = (array1, array2) -> {
        array1[0] = array1[0] > array2[0] ? array1[0] : array2[0];
        array1[1] = array1[1] > array2[1] ? array1[1] : array2[1];
        array1[2] = array1[2] < array2[2] ? array1[2] : array2[2];
        array1[3] = array1[3] < array2[3] ? array1[3] : array2[3];
    };

    int[] searchDimensions = Arrays.stream(dimensions).collect(supplier, accumulator, combiner);

    return applyInBetween(inputArray, function, searchDimensions[0], searchDimensions[1], searchDimensions[2], searchDimensions[3]);
}

答案 1 :(得分:1)

如何将元素收集到维度4的数组中的自定义收集器:

static class DimensionsMinMaxCollector implements Collector<Dimensions, int[], int[]> {

    @Override
    public BiConsumer<int[], Dimensions> accumulator() {
        return (array, dim) -> {
            array[0] = dim.startX > array[0] ? dim.startX : array[0];
            array[1] = dim.startY > array[1] ? dim.startY : array[1];
            array[2] = dim.endX > array[2] ? dim.endX : array[2];
            array[3] = dim.endY > array[3] ? dim.endY : array[3];
        };
    }

    @Override
    public Set<Characteristics> characteristics() {
        return EnumSet.of(Characteristics.IDENTITY_FINISH);
    }

    // TODO this looks like is not an identity for negative values
    @Override
    public BinaryOperator<int[]> combiner() {
        return (left, right) -> {
            for (int i = 0; i < 4; i++) {
                left[i] = left[i] > right[i] ? left[i] : right[i];
            }
            return left;
        };
    }

    @Override
    public Function<int[], int[]> finisher() {
        return Function.identity();
    }

    @Override
    public Supplier<int[]> supplier() {
        return () -> new int[4];
    }

}

答案 2 :(得分:1)

如果预期结果值与您要比较的属性相同,则无需使用自定义比较器,只需在获取最小值之前映射到属性。最大值。如果属性具有原始类型,则可以在简单性和效率方面具有额外的好处:

<S, T> T performOnIntersections(
         Function<S, T> function, S[][] inputArray, Dimensions...dimensions) {

    int maxStartX = Arrays.stream(dimensions).mapToInt(d -> d.startX).max().getAsInt();
    int maxStartY = Arrays.stream(dimensions).mapToInt(d -> d.startY).max().getAsInt();
    int minEndX = Arrays.stream(dimensions).mapToInt(d -> d.endX).min().getAsInt();
    int minEndY = Arrays.stream(dimensions).mapToInt(d -> d.endY).min().getAsInt();

    return applyInBetween(inputArray, function, maxStartX, maxStartY, minEndX, minEndY);
}

是否避免在普通阵列上进行多次迭代有任何好处,目前还不清楚。如果您想尝试,可以使用

<S, T> T performOnIntersections(
         Function<S, T> function, S[][] inputArray, Dimensions...dimensions){

    BiConsumer<Dimensions,Dimensions> join = (d1,d2) -> {
        d1.startX=Math.max(d1.startX, d2.startX);
        d1.startY=Math.max(d1.startY, d2.startY);
        d1.endX=Math.min(d1.endX, d2.endX);
        d1.endY=Math.min(d1.endY, d2.endY);
    };
    Dimensions d = Arrays.stream(dimensions).collect(
        () -> new Dimensions(Integer.MIN_VALUE,Integer.MIN_VALUE,
                             Integer.MAX_VALUE,Integer.MAX_VALUE),
        join, join);

    int maxStartX = d.startX;
    int maxStartY = d.startY;
    int minEndX = d.endX;
    int minEndY = d.endY;

    return applyInBetween(inputArray, function, maxStartX, maxStartY, minEndX, minEndY);
}

关键点是join函数,它将第一个参数调整为两个维度的交集。这称为 mutable reduction ,并避免在每次评估时创建新的Dimensions实例。为此,collect方法需要Supplier作为其第一个参数,它在中性初始状态下生成一个新实例,即跨越整个整数范围的Dimensions实例。为此,我假设您有一个构造函数接受初始startXstartYendXendY值。

也可以进行不可变的减少:

<S, T> T performOnIntersections(
         Function<S, T> function, S[][] inputArray, Dimensions...dimensions){

    Dimensions d = Arrays.stream(dimensions)
        .reduce((d1,d2) -> new Dimensions(
            Math.max(d1.startX, d2.startX),
            Math.max(d1.startY, d2.startY),
            Math.min(d1.endX, d2.endX),
            Math.min(d1.endY, d2.endY)))
        .get();

    int maxStartX = d.startX;
    int maxStartY = d.startY;
    int minEndX = d.endX;
    int minEndY = d.endY;

    return applyInBetween(inputArray, function, maxStartX, maxStartY, minEndX, minEndY);
}

对于较小的数组,这可能更有效(对于单个元素数组的特殊情况,它只返回元素)。这也适用于Dimensions的不可变版本。