我正在尝试学习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的功能解决同样的问题。
答案 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];
}
}
}
首先,使用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);
}