在顺序排序的流中查找缺少的整数

时间:2016-03-30 17:41:05

标签: java java-8 java-stream

假设我有一个列表

ArrayList<String> arr = new ArrayList(Arrays.asList("N1", "N2", "N3", "N5"));

我如何找到“N4”,我的意思是,我怎么发现丢失的整数是4?

到目前为止我尝试了什么

Integer missingID = arr.stream().map(p -> Integer.parseInt(p.substring(1))).sorted()
                .reduce((p1, p2) -> (p2 - p1) > 1 ? p1 + 1 : 0).get();

这不起作用,因为reduce不打算在这种情况下以我需要的方式工作,实际上,我不知道如何做到这一点。 如果没有丢失的号码,则下一个必须是"N6" - or just 6 -(在此示例中)

必须使用java标准流的库,不使用第三方。

6 个答案:

答案 0 :(得分:9)

这里要实现的算法基于this one:要找到整数序列中缺少的数字,诀窍是:

  • 计算序列中元素的总和。
  • 计算序列与缺失数字的元素之和:这很容易做到,因为我们可以确定来自{{的整数序列的和的最小值,最大值和we know 1}} minmax
  • 找出这两个总和之间的差异:那是我们缺少的数字

在这种情况下,我们可以首先映射到仅由数字本身组成的max*(max+1)/2 - (min-1)*min/2,然后调用summaryStatistics()来收集Stream的统计信息。这将返回一个IntSummaryStatistics,其中包含我们想要的所有值:min,max和sum:

IntStream

如果没有丢失号码,则会打印0。

答案 1 :(得分:4)

以下是我的免费StreamEx库中涉及pairMap操作的解决方案。它打印排序输入的所有缺失元素:

ArrayList<String> arr = new ArrayList(Arrays.asList("N1", "N2", "N3", "N5"));
StreamEx.of(arr).map(n -> Integer.parseInt(n.substring(1)))
                .pairMap((a, b) -> IntStream.range(a+1, b))
                .flatMapToInt(Function.identity())
                .forEach(System.out::println);

pairMap操作允许您将每个相邻的流对映射到其他位置。在这里,我们将它们映射到跳过的数字的流,然后展平这些流。

没有第三方库可以使用相同的解决方案,但看起来更详细:

ArrayList<String> arr = new ArrayList(Arrays.asList("N1", "N2", "N3", "N5"));
IntStream.range(0, arr.size()-1)
                .flatMap(idx -> IntStream.range(
                    Integer.parseInt(arr.get(idx).substring(1))+1,
                    Integer.parseInt(arr.get(idx+1).substring(1))))
                .forEach(System.out::println);

答案 2 :(得分:3)

如果数组中只有 ONE 缺失数字,并且所有数字都是正数,则可以使用XOR算法,如this question and its answers中所述:

value_range

这种方法的优点是您不需要使用额外的数据结构,只需要几个List<String> list = Arrays.asList("N5", "N2", "N3", "N6"); int xorArray = list.stream() .mapToInt(p -> Integer.parseInt(p.substring(1))) .reduce(0, (p1, p2) -> p1 ^ p2); int xorAll = IntStream.rangeClosed(2, 6) .reduce(0, (p1, p2) -> p1 ^ p2); System.out.println(xorArray ^ xorAll); // 4 s。

根据@ Holger的评论

编辑

此解决方案要求您事先知道数字的范围。虽然另一方面,它不需要对列表和流进行排序。

即使列表未排序,您仍然可以使用int获得minmax(因此,范围),但这需要额外的迭代。

答案 3 :(得分:2)

您可以创建一个状态对象,用于将单个输入流转换为多个缺失条目流。然后可以对这些缺失的条目流进行平面映射以生成单个输出:

public class GapCheck {
    private String last;

    public GapCheck(String first) {
        last = first;
    }

    public Stream<String> streamMissing(String next) {
        final int n = Integer.parseInt(next.replaceAll("N", ""));
        final int l = Integer.parseInt(last.replaceAll("N", ""));
        last = next;
        return IntStream.range(l + 1, n).mapToObj(Integer::toString);
    }
} 

用法:

final List<String> arr = new ArrayList(Arrays.asList("N1", "N3", "N5"));

arr.stream()
   .flatMap(new GapCheck(arr.get(0))::streamMissing)
   .forEach(System.out::println);

输出:

2
4

答案 4 :(得分:1)

这比您预期的更多,但可以通过collect来完成。

public class Main {
    public static void main(String[] args) {
        ArrayList<String> arr = new ArrayList<String>(Arrays.asList("N1", "N2", "N3", "N5", "N7", "N14"));

        Stream<Integer> st = arr.stream().map(p -> Integer.parseInt(p.substring(1))).sorted();
        Holder<Integer> holder = st.collect(() -> new Holder<Integer>(), 
                (h, i) -> {
                    Integer last = h.getProcessed().isEmpty() ? null : h.getProcessed().get(h.getProcessed().size() - 1);
                    if (last != null) {
                        while (i - last > 1) {
                            h.getMissing().add(++last);
                        }
                    }
                    h.getProcessed().add(i);
                }, 
                (h, h2) -> {});
        holder.getMissing().forEach(System.out::println);
    }

    private static class Holder<T> {
        private ArrayList<T> processed;
        private ArrayList<T> missing;

        public Holder() {
            this.processed = new ArrayList<>();
            this.missing = new ArrayList<>();
        }

        public ArrayList<T> getProcessed() {
            return this.processed;
        }

        public ArrayList<T> getMissing() {
            return this.missing;
        }
    }
}

打印

4
6
8
9
10
11
12
13

请注意,这种事情并不是特别适合Stream。所有的流处理方法都倾向于将每个项目一次性传递给你,所以你需要同时处理所有缺失数字的运行,最后,你要编写大量的代码来避免只编写一个循环。

答案 5 :(得分:1)

这是使用纯流的一种解决方案,虽然效率不高。

public void test() {
    List<String> arr = new ArrayList(
                    Arrays.asList("N1", "N2", "N3", "N5", "N7"));

    List<Integer> list = IntStream
                .range(1, arr.size())
                .mapToObj(t -> new AbstractMap.SimpleEntry<Integer, Integer>(
                        extract(arr, t), extract(arr, t) - extract(arr, t - 1)))
                .filter(t -> t.getValue() > 1)
                .map(t -> t.getKey() - 1)
                .collect(Collectors.toList());

    System.out.println(list);
}

private int extract(List<String> arr, int t) {
    return Integer.parseInt(arr.get(t).substring(1));
}

主要的性能块将是因为重复解析列表元素。但是,此解决方案将能够提供所有缺失的数字。