-3

The task is to check using Stream if such type of string "AZ6BYW59UO6CR8BNT7NM 284130" satisfies these conditions:

  1. 20 uppercase alphabetical chars or digits before the space, the number of digits is 6 strictly
  2. 6 digits after the space

Here is what I've done so far:

    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;
}

But as a stream cannot be reused, it is wrong, but I have no idea how to overcome the issue.

  • 2
    Related question: [Copy a stream to avoid “stream has already been operated upon or closed”](https://stackoverflow.com/questions/23860533/copy-a-stream-to-avoid-stream-has-already-been-operated-upon-or-closed) – geocodezip Jan 07 '21 at 16:29
  • You could create again the stream each time... but using regexp would be much simpler for such a task – Zinc Jan 07 '21 at 16:29
  • 2
    I would suggest not using streams, the code is not readable, there are most likely easier and better ways to achieve that logic. At least for the length calculations - why would use a stream for that?? – luk2302 Jan 07 '21 at 16:30
  • @luk2302 that's the task - to use stream – Evgeniy Kravtsov Jan 07 '21 at 22:53
  • @geocodezip thanks! Yes, I saw it, but as I've just started to learn java, I didn't get how can I apply it in my case. – Evgeniy Kravtsov Jan 07 '21 at 23:23

3 Answers3

1

As others have pointed out it's not necessary to use streams for this but it can be done. If I understood the requirements correctly then this should satisfy what you're after with streams.


  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;
  }

To test it:

    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

EDIT - I missed the fact the first part must have 6 digits

  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;
  }

I will caution that using this will work but the stream API is probably overkill. This solution also creates temporary objects during the map-reduce that could be avoided with other solutions. It's not a big deal for many cases but in a performance critical situation this could show up in profiling. You'd have to see how it compares to a regex or other solution in your own tests. This just answers the question using the approach requested.

zcourts
  • 4,863
  • 6
  • 49
  • 74
1

All you need is to match patterns so streams is not necessary or really appropriate. But, you can do it with a single stream using a 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");
}

Prints

   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

This splits on a single space and and the tries to match the required contents. If it gets thru, the count will be 1 and true will be returned. Otherwise, 0 and false.

  • \\w{20} will match a word of 20 letters and digits.
  • \\d{6} will match a number of 6 digits.
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;
}
WJS
  • 36,363
  • 4
  • 24
  • 39
1
  1. Make sure you check the size of the resulting array (i.e. the array obtained by splitting the given string) to avoid ArrayIndexOutOfBoundsException.
  2. Since you have to perform three independent operations on the left side of the substring, you will have to create a stream-supplier to construct a new stream with all intermediate operations already set up. Check this article to learn more about it.

Demo:

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;
    }
}

Output:

AZ6BYW59UO6CR8BNT7NM 284130 => true
AZ6BYW59UOXCR8BNT7NM 284130 => false
AZ6BYW59UO6CR8BNT7NM 7284130 => false
AbZ6BYW59UO6CR8BNT7NM 284130 => false
AZ6BYW59UO6CR8BNT7NM284130 => false
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110