从流中获取中间结果以供稍后在流中使用

Getting intermediate results from stream to be used later in stream

我正在尝试编写一些函数式编程代码(使用来自 Java 8 的 lambda 和流)来测试字符串中是否包含唯一字符(如果 它有, return true, 如果 , return false)。使用 vanilla Java 执行此操作的常见方法是使用类似集合的数据结构,即:

public static boolean oldSchoolMethod(String str) {
    Set<String> set = new HashSet<>();

    for(int i=0; i<str.length(); i++) {
        if(!set.add(str.charAt(i) + "")) return false;
    }
    return true;
}

集合 returns true 如果 character/object 可以添加到集合中(因为它以前不存在)。它 returns false 如果它不能(它已经存在于集合中,重复值,并且不能添加)。这使得打破循环并检测是否有重复变得容易,而无需遍历字符串的所有长度 N 个字符。

我知道在 Java 8 个流中你不能分开一个流。有没有办法 捕获 中间流操作的 return 值,比如添加到集合的 return 值(truefalse) 并将该值发送到管道的下一阶段(另一个中间操作或终端流操作)?即

Arrays.stream(myInputString.split(""))
    .forEach( i -> {
       set.add(i) // need to capture whether this returns "true" or "false" and use that value later in 
                  // the pipeline or is this bad/not possible?
    });

我想到的解决这个问题的其他方法之一是只使用 distinct() 并将结果收集到一个新字符串中,如果它与原始字符串的长度相同,那么你就知道了是唯一的,否则如果有不同的长度,一些字符会因为不明显而被过滤掉,因此你知道在比较长度时它不是唯一的。我在这里看到的唯一问题是你必须遍历字符串的所有长度 N 字符,其中“老派”方法最好的情况可以在几乎恒定的时间内完成 O (1),因为它正在打破循环并在发现 1 个重复字符后立即 returning:

public static boolean java8StreamMethod(String str) {
    String result = Arrays.stream(str.split(""))
            .distinct()
            .collect(Collectors.joining());

    return result.length() == str.length();
}

此代码可能适合您:

public class Test {

    public static void main(String[] args) {

        String myInputString = "hellowrd";

        HashSet<String> set = new HashSet<>();
        Optional<String> duplicateChar =Arrays.stream(myInputString.split("")).
                filter(num-> !set.add(num)).findFirst();
        if(duplicateChar.isPresent()){
            System.out.println("Not unique");
        }else{
            System.out.println("Unique");
        }

    }
}

这里使用 findFirst() 我能够找到第一个重复元素。这样我们就不需要继续迭代其余的字符了。

如果只映射到布尔值呢?

Arrays.stream(myInputString.split(""))
   .map(set::add)
   .<...>

我想这会解决你的具体问题,但这不是一个很好的解决方案,因为流链中的闭包不应该有副作用(这正是函数式编程的重点......)。

有时经典的 for 循环仍然是某些问题的更好选择;-)

您的解决方案都在执行不必要的字符串操作。

例如您可以使用 Set<Character>:

而不是使用 Set<String>
public static boolean betterOldSchoolMethod(String str) {
    Set<Character> set = new HashSet<>();
    for(int i=0; i<str.length(); i++) {
        if(!set.add(str.charAt(i))) return false;
    }
    return true;
}

但是从charCharacter的拳击也是可以避免的。

public static boolean evenBetterOldSchoolMethod(String str) {
    BitSet set = new BitSet();
    for(int i=0; i<str.length(); i++) {
        if(set.get(str.charAt(i))) return false;
        set.set(str.charAt(i));
    }
    return true;
}

同样,对于 Stream 变体,您可以使用 str.chars() 而不是 Arrays.stream(str.split(""))。此外,您可以使用 count() 而不是通过 collect(Collectors.joining()) 将所有元素收集到一个字符串中,只需在其上调用 length()

解决这两个问题得到解决方案:

public static boolean newMethod(String str) {
    return str.chars().distinct().count() == str.length();
}

这很简单,但是缺少短路。此外,distinct() 的性能特征是依赖于实现的。在 OpenJDK 中,它在后台使用普通的 HashSet,而不是 BitSet 或类似的东西。