今天我正在做一些来自Codewars的katas。我必须编写函数,它只保留数组中相同元素的N,例如:
{1,2,3,4,1}, N=1 -> {1,2,3,4}
{2,2,2,2}, N=2 -> {2,2}
我使用流来提出这个解决方案:
public static int[] deleteNth(int[] elements, int maxOcurrences) {
List<Integer> ints = Arrays.stream(elements)
.boxed()
.collect(Collectors.toList());
return ints.stream().filter(x -> Collections.frequency(ints, x) <= maxOcurrences)
.mapToInt(Integer::intValue)
.toArray();
}
因此,首先将整数更改为整数,然后在freq高于N时进行过滤。 但这并不起作用,因为无论其位置如何,重复元素的频率都相同。看起来过滤器调用后会过滤值。如何解决此问题以获得正确的值?
PS:我知道那是O(n ^ 2),但对我来说这不是问题。答案 0 :(得分:3)
我发现完成手头任务的解决方案如下:
public static int[] deleteNth(int[] elements, int maxOccurrences) {
return Arrays.stream(elements)
.boxed()
.collect(Collectors.groupingBy(Function.identity(),
LinkedHashMap::new,
Collectors.counting()))
.entrySet()
.stream()
.flatMapToInt(entry ->
IntStream.generate(entry::getKey)
.limit(Math.min(maxOccurrences, entry.getValue())))
.toArray();
}
我们首先对元素进行分组,然后应用Collectors.counting()
作为下游收集器来获取给定元素的计数。完成之后,我们只需映射给定数量n
次,然后收集到具有toArray
急切操作的数组。
答案 1 :(得分:2)
实际上,您排除了优于maxOcurrences
值的元素:
.filter(x -> Collections.frequency(ints, x) <= maxOcurrences)
我不确定完整的Stream
解决方案是否是此用例的最佳选择,因为您希望根据当前收集的数量添加一些值&#34;对于这些价值观。
这是我如何实现的:
public class DeleteN {
public static void main(String[] args) {
System.out.println(Arrays.toString(deleteNth(new int[] { 1, 2, 3, 4, 1 }, 1)));
System.out.println(Arrays.toString(deleteNth(new int[] { 2, 2, 2, 2 }, 2)));
}
public static int[] deleteNth(int[] elements, int maxOcurrences) {
Map<Integer, Long> actualOccurencesByNumber = new HashMap<>();
List<Integer> result = new ArrayList<>();
Arrays.stream(elements)
.forEach(i -> {
Long actualValue = actualOccurencesByNumber.computeIfAbsent(i, k -> Long.valueOf(0L));
if (actualValue < maxOcurrences) {
result.add(i);
actualOccurencesByNumber.computeIfPresent(i, (k, v) -> v + 1L);
}
});
return result.stream().mapToInt(i -> i).toArray();
}
}
输出:
[1,2,3,4]
[2,2]
答案 2 :(得分:1)
我认为这是一个不使用流的好例子。当涉及有状态操作时,流并不总是最好的方法。
但它确实可以完成,而且问题也专门针对流,所以你可以使用以下内容。
使用forEachOrdered
您可以使用forEachOrdered
来确保订单(此处显示流必须顺序):
public static int[] deleteNth(int[] elements, int maxOcurrs) {
List<Integer> list = new ArrayList<>();
Arrays.stream(elements).forEachOrdered(elem -> {
if (Collections.frequency(list, elem) < maxOcurrs) list.add(elem);
});
return list.stream().mapToInt(Integer::intValue).toArray();
}
使用收集
鉴于一些环境问题,您可以使用collect
方法来完成此任务。
当流有序和顺序时({1}}的情况),Arrays.stream(elements).boxed()
方法不使用组合运算符( 这是java8和java9当前版本的真实情况,但是不能保证在下一版本中完全相同,因为可以进行许多优化)。
此实现保持流的顺序,如前所述,在当前版本中正常工作。就像下面链接中的答案所说的那样,并且在我个人看来,我发现连续流中collect()
的实现将需要使用组合器非常困难。
collect方法的代码如下:
collect
这个public static int[] deleteNth(int[] elements, int maxOcurrs) {
return Arrays.stream(elements).boxed()
.collect(() -> new ArrayList<Integer>(),
(list, elem) -> {
if (Collections.frequency(list, elem) < maxOcurrs) list.add(elem);
},
(list1, list2) -> {
throw new UnsupportedOperationException("Undefined combiner");
})
.stream()
.mapToInt(Integer::intValue)
.toArray();
}
创建一个collector
,当goind添加新元素时检查是否满足ArrayList
,如果不满足,则添加元素。如前所述,在下面的答案中,根本没有调用组合器。这比maxOcurrences
好一点。
有关为什么未在顺序流中调用组合器方法的更多信息,请参见here。