从流中收集某些元素

时间:2015-09-09 14:10:21

标签: java java-8 java-stream

如何从Stream中提取两个元素?例如,我试图从Stream<String>中提取元素0和1(这些数字是任意的!)。一个天真的方法是:

List<String> strings = Arrays.asList("s0", "s1", "s2", "s3", "s4");
Consumer<String> c0 = s -> System.out.println("c0.accept(" + s + ")");
Consumer<String> c1 = s -> System.out.println("c1.accept(" + s + ")");
strings.stream().skip(0).peek(c0).skip(1).peek(c1).findAny();

这会产生以下输出:

c0.accept(s0)
c0.accept(s1)
c1.accept(s1)

我理解,这是因为s0将进入流,遇到skip(0),然后peek(c0)(给出第一行)然后skip(1),这将跳过这个元素,然后显然继续从流的开头的下一个元素。

我以为我可以使用这些消费者来提取字符串,但c0会被第二个元素覆盖:

String[] extracted = new String[2];
c0 = s -> extracted[0];
c1 = s -> extracted[1];

编辑:

这些是流的特征:

  • 只有一个流,而不是列表或数组
  • 流可能是无限的
  • 可以制作流sequential

4 个答案:

答案 0 :(得分:4)

鉴于您的限制,您可以将dispatch_async(dispatch_get_main_queue())与自定义收集器结合使用,如下所示:

//...
if checkFileExists(saveURL) { // saveURL: NSURL
    dispatch_async(dispatch_get_main_queue()) {
        let message = "File named `\(saveURL.lastPathComponent!)` already exists. Please import using a new name or else cancel."
        let alertController = UIAlertController(title: "", message: message, preferredStyle: UIAlertControllerStyle.Alert)
        alertController.addTextFieldWithConfigurationHandler { textField -> Void in
            textField.text = saveURL.lastPathComponent! // it presents a text field containing the file name which needs to be changed
        }

        func presentAlertController() {
            self.presentViewController(alertController, animated: true) {}
        }

        alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))

        alertController.addAction(UIAlertAction(title: "Rename", style: .Default) { action -> Void in
            saveURL = saveURL.URLByDeletingLastPathComponent!.URLByAppendingPathComponent((alertController.textFields?.first!.text)!)
            if checkFileExists(saveURL) {
                presentAlertController() // currently it is calling a function to present the UIAlertController again if the file still exists when the button is clicked
            } else {
                saveXML(saveURL, dataObject: self.myThing)
                self.fileTableView.reloadData()
            }
        })
        presentAlertController() // this will be the first time that this called
    }
}
//...

此处limit()是包含所需元素索引的集合(不限于2)。用法:

public static <T, A, R> Collector<T, ?, R> collectByIndex(Set<Integer> wantedIndices, 
                                                          Collector<T, A, R> downstream) {
    class Acc {
        int pos;
        A acc = downstream.supplier().get();
    }
    return Collector.of(Acc::new, (acc, t) -> {
        if(wantedIndices.contains(acc.pos++))
            downstream.accumulator().accept(acc.acc, t);
    }, (a, b) -> {throw new UnsupportedOperationException();}, // combining not supported
       acc -> downstream.finisher().apply(acc.acc));
}

答案 1 :(得分:2)

这是我在其他答案中看不到的方法。它使用了无处不在的Pair类的变体:

class Pair<T> {
    final T first;
    final T last;
    Pair(T t1, T t2) { first = t1; last = t2; }
    Pair(T t) { first = last = t; }
    Pair<T> merge(Pair<T> other) { return new Pair<>(this.first, other.last); }
}

完成此操作后,您可以轻松获取流的第一个和最后一个元素。给定无限流和所需索引,您可以使用skip()limit()修剪流以仅包含所需元素:

static <T> Pair<T> firstAndLast(Stream<T> stream, int firstIndex, int lastIndex) {
    // ensure indexes >= 0 and firstIndex <= lastIndex
    return stream.skip(firstIndex)
                 .limit(lastIndex - firstIndex + 1)
                 .map(Pair::new)
                 .reduce(Pair::merge)
                 .orElseThrow(() -> new IllegalArgumentException("nonexistent"));
}

其他变体包括将构造或合并逻辑内联到流操作中,而不是在Pair类上。重构品味。

您可以这样使用它:

    Stream<String> str = Stream.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j");
    Pair<String> pair = firstAndLast(str, 4, 5);
    System.out.println(pair.first + " " + pair.last);

    e f

答案 2 :(得分:1)

此解决方案来自Federico Peralta Schaffner的评论:

public String[] collect(Stream<String> stream, int... positions) {
    String[] collect = new String[positions.length];
    Iterator<String> iterator = stream.iterator();
    int skipped = 0;
    for (int pos = 0; pos < positions.length; pos++) {
        while (skipped++ < positions[pos]) {
            iterator.next();
        }
        collect[pos] = iterator.next();
    }
    return collect;
}

这是最直接,最直接的想法,效果很好。

答案 3 :(得分:0)

您可以写下类似内容:

public static void main(String[] args) throws Exception {
    List<String> strings = Arrays.asList("s0", "s1", "s2", "s3", "s4");
    System.out.println(getNthElement(strings.stream(), 0)); // prints "s0"
    System.out.println(getNthElement(strings.stream(), 1)); // prints "s1"
}

private static <T> T getNthElement(Stream<T> stream, int n) {
    return stream.skip(n).findFirst().get();
}

请注意,如果流中的元素少于n,则会抛出异常。此外,仅当Stream不是并行时才有意义。