【问题标题】:Validate a string using java 8 Stream. How to overcome "stream has already been operated upon or closed"?使用 java 8 Stream 验证字符串。如何克服“流已被操作或关闭”?
【发布时间】:2021-04-13 09:43:48
【问题描述】:

任务是使用 Stream 检查这种类型的字符串“AZ6BYW59UO6CR8BNT7NM 284130”是否满足这些条件:

  1. 空格前20个大写字母或数字,位数严格为6位
  2. 空格后6位

这是我到目前为止所做的:

    public static boolean validateCode(String input)
{
    String[] words = input.split("\\s+");
    ArrayList<String> wordList = new ArrayList<String>(Arrays.asList(words));

    Stream<Character> left = wordList.get(0).chars().mapToObj(ch -> (char)ch);
    Stream<Character> right = wordList.get(1).chars().mapToObj(ch -> (char)ch);

   boolean leftIs20 = left
            .collect(Collectors.counting()).equals(20L);

    boolean leftisAlfaDigit = left
            .allMatch(x -> (Character.isDigit(x) || Character.isUpperCase(x)));

     boolean leftis6Digits = left.filter(x -> Character.isDigit(x)).equals(6L);

    boolean rightAreDigits = right.allMatch(x ->Character.isDigit(x));

    boolean rightAre6Digits = right.collect(Collectors.counting()).equals(6L);
    
    return leftIs20 && leftisAlfaDigit && leftis6Digits && rightAreDigits && rightAre6Digits;
}

但是由于流不能被重用,这是错误的,但我不知道如何克服这个问题。

【问题讨论】:

  • 您可以每次都重新创建流...但是对于这样的任务,使用正则表达式会简单得多
  • 我建议不要使用流,代码不可读,很可能有更简单更好的方法来实现该逻辑。至少对于长度计算 - 为什么要为此使用流??
  • @luk2302 这就是任务 - 使用流
  • @geocodezip 谢谢!是的,我看到了,但是因为我刚刚开始学习 java,所以我不知道如何在我的情况下应用它。

标签: java string arraylist java-stream


【解决方案1】:
  1. 确保检查结果数组的大小(即通过拆分给定字符串获得的数组)以避免ArrayIndexOutOfBoundsException
  2. 由于您必须在子字符串的左侧执行三个独立的操作,因此您必须创建一个流供应商来构造一个新的流,其中所有中间操作都已设置。查看this article 了解更多信息。

演示:

import java.util.function.Supplier;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        // Test
        String[] testStrs = { "AZ6BYW59UO6CR8BNT7NM 284130", "AZ6BYW59UOXCR8BNT7NM 284130",
                "AZ6BYW59UO6CR8BNT7NM 7284130", "AbZ6BYW59UO6CR8BNT7NM 284130", "AZ6BYW59UO6CR8BNT7NM284130" };
        for (String s : testStrs) {
            System.out.println(s + " => " + validateCode(s));
        }
    }

    public static boolean validateCode(String input) {
        int leftAllCount = 20, leftDigCount = 6, rightDigCount = 6;
        String[] words = input.split("\\s+");
        if (words.length >= 2) {
            Supplier<Stream<Character>> streamSupplierLeft = () -> words[0].chars().mapToObj(ch -> (char) ch);
            Stream<Character> right = words[1].chars().mapToObj(ch -> (char) ch);
            return streamSupplierLeft.get().count() == leftAllCount
                    && streamSupplierLeft.get().filter(Character::isDigit).count() == leftDigCount
                    && streamSupplierLeft.get().filter(Character::isUpperCase).count() == (leftAllCount - leftDigCount)
                    && right.filter(Character::isDigit).count() == rightDigCount;
        }
        return false;
    }
}

输出:

AZ6BYW59UO6CR8BNT7NM 284130 => true
AZ6BYW59UOXCR8BNT7NM 284130 => false
AZ6BYW59UO6CR8BNT7NM 7284130 => false
AbZ6BYW59UO6CR8BNT7NM 284130 => false
AZ6BYW59UO6CR8BNT7NM284130 => false

【讨论】:

    【解决方案2】:

    您所需要的只是匹配模式,因此流不是必需的或真正合适的。但是,您可以使用 filter 对单个流进行操作

    String[] data = { "AZ6BYW59UO6CR8BNT7NM 284130",  // valid
                     "AZ6BYW59UO6&C8BNT7NM 2841S0",   // non letter or digit (&)             
            "MMMMMMMMMMMMMMMMMMMM 823820",            // not enough digits
            "111111MMMMMMMMMMMMMM 228130",            // valid
            "11111111111111111111 228130",            // too may digits
            "AZ6BYW59UO6CR8BT7NM 284130",             // first word too short
            "AZ6BYW59UO6CR8BNT7NM 2824130",           // seven digits
            "AZ6BYW59UO6CRs8BNT7NM 284130",           // first word too long
            "AZ6BYW59UO6CR8BNT7NM    282230"          // too many spaces
        "AZ6BYW59UO6CR8BNT7NM 28230" };               // five digits
    
    
    
    for (String text : data) {
        System.out.printf("%30s -> %s%n", text, validateCode(text) ? "Valid" : "Invalid");
    }
    

    打印

       AZ6BYW59UO6CR8BNT7NM 284130 -> Valid
       AZ6BYW59UO6&C8BNT7NM 2841S0 -> Invalid
       MMMMMMMMMMMMMMMMMMMM 823820 -> Invalid
       111111MMMMMMMMMMMMMM 228130 -> Valid
       11111111111111111111 228130 -> Invalid
        AZ6BYW59UO6CR8BT7NM 284130 -> Invalid
      AZ6BYW59UO6CR8BNT7NM 2824130 -> Invalid
      AZ6BYW59UO6CRs8BNT7NM 284130 -> Invalid
    AZ6BYW59UO6CR8BNT7NM    282230 -> Invalid
        AZ6BYW59UO6CR8BNT7NM 28230 -> Invalid
    
    

    这会拆分为一个空格,并尝试匹配所需的内容。如果它通过,计数将为 1 并返回 true。否则为 0 和 false。

    • \\w{20} 将匹配 20 个字母和数字的单词。
    • \\d{6} 将匹配 6 位数字。
    public static boolean validateCode(String text) {
        return Stream.<String[]>of(text.split("\\s"))
                .filter(s -> s.length == 2 && s[0].matches("\\w{20}") && s[0].chars()
                        .filter(Character::isDigit)
                        .count() == 6 && s[1].matches("\\d{6}"))
                .count() == 1;
    }
    

    【讨论】:

      【解决方案3】:

      正如其他人指出的那样,没有必要为此使用流,但可以这样做。如果我正确理解了这些要求,那么这应该可以满足您对流的需求。

      
        private static boolean validateCode(String in) {
          //The task is to check using Stream if such type of string "AZ6BYW59UO6CR8BNT7NM 284130" satisfies these conditions:
          //20 uppercase alphabetical chars or digits before the space, the number of digits is 6 strictly
          //6 digits after the space
          final var input = in.split("\\s");
          final var a = input[0];
          final var b = input[1];
          return a.chars().filter(c -> Character.isDigit(c) || (Character.isAlphabetic(c) && Character.isUpperCase(c))).count() == 20 &&
            b.chars().filter(Character::isDigit).count() == 6;
        }
      

      测试它:

          System.err.println(validateCode("AZ6BYW59UO6CR8BNT7NM 284130")); //valid
          System.err.println(validateCode("Z6BYW59UO6CR8BNT7NM 284130")); //less than 20
          System.err.println(validateCode("A$6BYW59UO6CR8BNT7NM 284130")); //non alpha numeric
          System.err.println(validateCode("AZ6BYW59UO6CR8BNT7NM 284k30")); //non-digit in end
          System.err.println(validateCode("AZ6BYW59UO6CR8BNT7NM 28413")); //less than 6
          System.err.println(validateCode("AZ6BYW59UO6CR8BNT7NM 2841302")); //more than 6
      

      编辑 - 我错过了第一部分必须有 6 位数字的事实

        private static boolean validateCode(String in) {
          final var input = in.split("\\s");
          final var a = input[0];
          final var b = input[1];
          final var prefixCounts = a.chars()
            .mapToObj(c -> Map.entry(Character.isDigit(c) ? 1 : 0, Character.isAlphabetic(c) && Character.isUpperCase(c) ? 1 : 0))
            .reduce(Map.entry(0, 0), (lhs, rhs) -> Map.entry(lhs.getKey() + rhs.getKey(), lhs.getValue() + rhs.getValue()));
          return prefixCounts.getKey() == 6 && prefixCounts.getValue() == 14 && //6 digits and 14 upper case letters = 20 total
            b.chars().filter(Character::isDigit).count() == 6;
        }
      

      我会警告说,使用它会起作用,但流 API 可能是矫枉过正。此解决方案还会在 map-reduce 期间创建临时对象,其他解决方案可以避免这些临时对象。在很多情况下这没什么大不了的,但在性能关键的情况下,这可能会出现在分析中。您必须在自己的测试中查看它与正则表达式或其他解决方案的比较。这只是使用请求的方法回答问题。

      【讨论】:

      • 左边部分必须有6位数字。
      • 非常感谢,您的解决方案非常优雅!
      • @LiveandLetLive,感谢您的通知!我设法做到了,只需添加一行&amp;&amp; a.chars().filter(c -&gt; Character.isDigit(c)).count() == 6
      • @EvgeniyKravtsov - 是的,这是一种方法。如果您只想迭代一次,您可以使用已编辑的解决方案将流转换为计数,然后减少以获得符合要求的最终字符数。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多