删除值仅保留N次

时间:2018-03-27 20:13:19

标签: java-8 java-stream

今天我正在做一些来自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),但对我来说这不是问题。

3 个答案:

答案 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