Java 8 - 在数组中查找领导者

时间:2017-06-02 00:26:52

标签: lambda functional-programming java-8

我正在尝试学习Java 8的功能,尤其是它的函数式编程方面。所以我试图解决一个问题:在数组中查找领导者 - 领导者是一个比右侧数组中的所有元素都大的元素。

例如:

输入数组: {98,23,54,12,20,7,27}

输出: 领导人 - 27 54 98

现在,我已经使用通常的迭代方法解决了这个问题,如下所示。

private static void findLeaders(int[] array) {
        int currentLeader = array[array.length - 1];
        System.out.println(currentLeader);
        for(int i = array.length - 1; i >= 0; i--) {
            if(array[i] > currentLeader) {
                System.out.println(array[i]);
                currentLeader = array[i];
            }
        }
    }

我尝试使用Java 8解决它,但除了编写这段代码之外我做不了多少,这又有编译错误:

Function<Integer, Integer> checkLeader = i ->  i > currentLeader ? i : currentLeader;
  

错误:在封闭范围内定义的局部变量currentLeader必须   是最终的或有效的最终

现在如何使用Java 8的功能解决同样的问题。

2 个答案:

答案 0 :(得分:5)

对我而言,传统的,强制性的方法似乎是最好的方法,同时考虑到性能和可读性/易维护性。尽管如此,这是我尝试使用流和一些函数式编程:

List<Integer> leaders = IntStream.rangeClosed(1, array.length)
    .mapToObj(i -> array[array.length - i])
    .collect(toLeaders());

在这里,我创建一个1..n封闭范围,然后在mapToObj内将索引1转换为n - 1,将2索引为{{1} }}, 等等。我立即使用这个转换后的索引来获取数组的相应元素,最后在自定义收集器的帮助下将其收集到列表中。此自定义收集器由n - 2辅助方法返回:

toLeaders()

private static Collector<Integer, ?, List<Integer>> toLeaders() { BiConsumer<List<Integer>, Integer> accumulator = (leaders, n) -> { if (leaders.isEmpty() || n > leaders.get(leaders.size() - 1)) { leaders.add(n); } }; return Collector.of(ArrayList::new, accumulator, (leaders1, leaders2) -> { leaders2.forEach(n -> accumulator.accept(leaders1, n)); return leaders1; }); } 接受两个值:包含到目前为止找到的领导者的列表,以及来自流的元素。此Biconsumer检查给定的数字是否为领导者,如果检查成功,则将该数字添加到给定的领导者列表中。

然后,使用此累加器的收集器是通过Collector.of实用程序创建的,该实用程序还接受将保留领导者的Supplier可变结构(这是BiConsumer<List<Integer>, Integer> accumulator )和BinaryOperator组合器,负责合并先前创建的两个领导者列表(仅在流并行时使用)。该组合器使用先前声明的ArrayList::new双音素。

答案 1 :(得分:3)

这个怎么样?您可以使用IntBinaryOperator代替Function<Integer, Integer>,因为checkLeader不是有效最终变量,因此无法在lambda表达式中访问它,但您可以将checkLeader作为参数传递给lambda表达式:

private static void findLeaders(int[] array) {
    IntBinaryOperator checkLeader = (left, right) -> left > right ? left : right;

    int currentLeader = array[array.length - 1];
    System.out.println(currentLeader);
    for (int i = array.length - 1; i >= 0; i--) {
        //  pass `currentLeader` for each call ---v
        if (checkLeader.applyAsInt(array[i], currentLeader) == array[i]) {
            System.out.println(array[i]);
            currentLeader = array[i];
        }
    }
}

让我们一步一步地使用stream api

进行重构

首先,使用IntBinaryOperator无法知道checkLeader是否小于array[i],因为它会返回最终较大的结果(checkLeader时== array[i]然后程序有错误)。所以你需要Comparator而不是IntBinaryOperator

private static void findLeaders(int[] array) {
    Comparator<Integer> comparator = Integer::compareTo;

    int currentLeader = array[array.length - 1];
    System.out.println(currentLeader);
    for (int i = array.length - 1; i >= 0; i--) {
        //  check whether array[i] > currentLeader   ---v
        if (comparator.compare(array[i], currentLeader) > 0) {
            System.out.println(array[i]);
            currentLeader = array[i];
        }
    }
}

然后我们可以使用Comparator#nullsFirst删除第一个println语句,因为它在循环中重复:

private static void findLeaders(int[] array) {
    Comparator<Integer> comparator = Comparator.nullsFirst(Integer::compareTo);

    Integer currentLeader = null;
    for (int i = array.length - 1; i >= 0; i--) {
        //  array[0] is always > currentLeader since it is null at the first time
        //                                              |
        if (comparator.compare(array[i], currentLeader) > 0) {
            System.out.println(array[i]);
            currentLeader = array[i];
        }
    }
}

准备以使用stream-api我们需要使用Stack将显示逻辑与收集逻辑分开:

private static void findLeaders(int[] array) {
    Comparator<Integer> comparator = Comparator.nullsFirst(Integer::compareTo);
    Stack<Integer> stack = new Stack<>();
    stack.push(null);

    for (int i = array.length - 1; i >= 0; i--) {
        //  comparing array[i] with the top leaders ---v
        if (comparator.compare(array[i], stack.peek()) > 0) {
            stack.push(array[i]);
        }
    }

//take 1st element away from stack since it is just a initial value for comparision
    //                               |
    System.out.println(stack.subList(1, stack.size()));
}

使用 IntStream#range 反向数组,然后我们可以改为使用for-each循环:

private static void findLeaders(int[] array) {
    Comparator<Integer> comparator = Comparator.nullsFirst(Integer::compareTo);
    Stack<Integer> stack = new Stack<>();
    stack.push(null);

    int[] reversed = IntStream.range(0, array.length)
            //      v--- reverse array elements
            .map(i -> array[array.length - i - 1]).toArray();

    for (int candidate : reversed) {
        if (comparator.compare(candidate, stack.peek()) > 0) {
            stack.push(candidate);
        }
    }

    System.out.println(stack.subList(1, stack.size()));
}

然后我们可以使用IntStream#collect内联除comparator之外的一行中的所有语句,但等待,Stack#peek会抛出错误,因为堆栈是空:

private static void findLeaders(int[] array) {
    Comparator<Integer> comparator = Comparator.nullsFirst(Integer::compareTo);

    List<Integer> result = IntStream.range(0, array.length)
            .map(i -> array[array.length - i - 1])
            .collect(
                    Stack::new,
                    (stack, candidate) -> {
                        // failed at the first time since the stack is empty
                        //                                     |
                        if (comparator.compare(candidate, stack.peek()) > 0) {
                            stack.push(candidate);
                        }
                    },
                    Stack::addAll
            );

    System.out.println(result.subList(1, result.size()));
}
通过检查堆栈是否为空来

修复错误:

private static void findLeaders(int[] array) {
    Comparator<Integer> c = Comparator.nullsFirst(Integer::compareTo);

    List<Integer> result = IntStream.range(0, array.length)
        .map(i -> array[array.length - i - 1])
        .collect(
            Stack::new,
            (stack, candidate) -> {
                //        v-- just add 1st candidate when stack is empty
                if (stack.isEmpty() || c.compare(candidate, stack.peek()) > 0) {
                    stack.push(candidate);
                }
            },
            Stack::addAll
        );

    System.out.println(result);
}

最后我们可以使用三元运算符?:来内联lambda表达式主体:

private static void findLeaders(int[] array) {
    Comparator<Integer> comparator = Comparator.nullsFirst(Integer::compareTo);

    List<Integer> result = IntStream.range(0, array.length)
        .map(i -> array[array.length - i - 1])
        .collect(
            Stack::new,
            (stack, candidate) -> stack.push(
               stack.isEmpty() || comparator.compare(candidate, stack.peek()) > 0 ?
                            candidate :
                            stack.pop()
            ),
            Stack::addAll
        );

    System.out.println(result);
}

修改

感谢@FedericoPeraltaSchaffner审核我的回答,现在不需要Comparator#nullsFirst,因为代码永远不会推送null初始值进行比较。

private static void findLeaders(int[] array) {
    Comparator<Integer> comparator = Integer::compareTo;

    List<Integer> result = IntStream.rangeClosed(1, array.length)
        .map(i -> array[array.length - i])
        .collect(
            Stack::new,
            (stack, candidate) -> stack.push(
              stack.isEmpty() || comparator.compare(candidate, stack.peek()) > 0 ?
                 candidate :
                 stack.pop()
                 //    ^--- just push the top back to the stack if top<=candidate
            ),
            Stack::addAll
        );

    System.out.println(result);
}