Home > Software engineering >  Scanner.hasNext() Causing Input to be Needed Twice
Scanner.hasNext() Causing Input to be Needed Twice

Time:09-15

I am working on some homework and have encountered a problem. The program creates a Tree, outputs pre/in/postorder traversals, then saves them to multiple files. The problem I'm having is getting the input. When you type in the input, after you're done typing in what should be the values of different Nodes, you may either type "done" or "exit". "Done" continues the program with the given input, while "exit" just closes the program. The weird thing is, you don't need to type the values of the Nodes twice to create those Nodes later on in the code, but you do have to type "done" or "exit" twice to get the program to listen to you. The code in question is here:

//gets input from console until "exit" or "done" is typed
response = scanner.next();
while(scanner.hasNext() && !response.equals("exit") && !response.equals("done")) {
    fileWriter.write(response   " ");
    response = scanner.next();
}

I have found that removing scanner.hasNext() gets it to function properly, but then it takes away sensing the EOF which is necessary for this assignment.

Here are the full three classes used in the program: P0:

/**
 * Sources for this class:
 * https://www.w3schools.com/java/java_regex.asp
 * https://stackoverflow.com/questions/14353947/how-to-represent-a-single-space-character-in-a-square-bracket-group
 * https://upload.wikimedia.org/wikipedia/commons/1/1b/ASCII-Table-wide.svg
 * 
 * Sources for shell script and makefile:
 * https://www.cyberciti.biz/faq/run-execute-sh-shell-script/
 * https://www.dummies.com/article/technology/computers/operating-systems/linux/linux-how-to-run-make-150284/
 * https://www.cs.swarthmore.edu/~newhall/unixhelp/javamakefiles.html
 */





package mikefitzgibbon.p0;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;

public class P0{
    public static void main(String args[]) {
        File file = new File("file");
        String input = "";
        
        //there can only be one or 0 arguments in the command line operation
        if(args.length > 1) {
            System.out.println("Please only enter the name of 1 file.");
            System.exit(1);
        }
        //if a filename is specified, try getting the input from it
        else if(args.length == 1){
            file = new File(args[0]);
        }
        //else read from System.in
        else {
            Scanner scanner = new Scanner(System.in);
            String response;
            
            file = new File("input");
            try (FileWriter fileWriter = new FileWriter(file)) {
                System.out.println("Please enter strings to put into the tree.");
                System.out.println("Type \"done\" to end taking in input, and type \"exit\" to prematurely quit.");

                //gets input from console until "exit" or "done" is typed
                response = scanner.next();
                while(scanner.hasNext() && !response.equals("exit") && !response.equals("done")) {
                    fileWriter.write(response   " ");
                    response = scanner.next();
                }

                //exits the program with OK return value
                if(response.equals("exit")) {
                    System.out.println("Prematurely ending program now.");
                    System.exit(0);
                }
            }
            catch(IOException e){
                System.out.println("Had trouble writing to file from System.in.");
            }
        }
        
        //scans file for strings until there are no more data or eof is reached
        try(Scanner scanner = new Scanner(file)){
            while(scanner.hasNextLine()){
               input  = scanner.nextLine()   " ";
            }
            scanner.close();
        }
        //exit the program is file name is invalid
        catch(FileNotFoundException e){
            System.out.println("The file name you entered does not exist.");
            System.exit(2);
        }

        //checks input for characters outside of the appropriate ASCII range
        for(char c : input.toCharArray()){
            if(!Character.isLetterOrDigit(c) &! Character.isWhitespace(c)) {
                System.out.println("Your file or console input was not readable."
                          "\nPlease only use alhpanumeric characters.");
                System.exit(3);
            }
        }
        
        //this is only used as a reference for the filename
        if(file.getName().equals("input"))
            file = new File("output");
            
        //creates the tree from the input and then converts it to the output
        Tree tree = new Tree(input);
        
        //displays the input
        System.out.println("Here is your input for the program.");
        System.out.println(input);
        System.out.println();

        //writes to the files
        System.out.println("Outputting data to files "   
                file.getName()   ".preorder, "   
                file.getName()   ".inorder, and "   
                file.getName()   ".postorder.");
        System.out.println();
        System.out.println("Output for "   file  ".preorder: ");
        tree.printPreorder(file.getName());
        System.out.println();
        System.out.println("Output for "   file  ".inorder: ");
        tree.printInorder(file.getName());
        System.out.println();
        System.out.println("Output for "   file  ".postorder: ");
        tree.printPostorder(file.getName());
        System.out.println();

        System.out.println("Ending program now.");
    }

}

Tree:

/**
 * Sources for this class:
 * https://www.w3schools.com/java/java_files_create.asp
 * https://www.softwaretestinghelp.com/binary-search-tree-in-java/
 * https://www.youtube.com/watch?v=WLvU5EQVZqY&ab_channel=TECHDOSE
 */


package mikefitzgibbon.p0;

import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;

public class Tree {
    //nodes that make up this part of the tree
    Node root;

    //constructor calls the buildTree() function
    public Tree(String input){
        buildTree(input);
    }
    
    //takes a list of strings and a root node and creates the tree structure recursively using the addNode helper function
    private void buildTree(String input) {
        Scanner scanner = new Scanner(input);
        
        //recursively builds tree with each word
        while(scanner.hasNext()){
            root = addNode(root,scanner.next());
        }
    }
    
    private Node addNode(Node root, String value){
        //checks root for null and sets it if it is
        if(root == null){
            root = new Node(value);
            return root;
        }
        //compares the first letter of the input to the root's value and recursively traverses tere
        else if(root.getValue().charAt(0) > value.charAt(0))
            root.setLeftNode(addNode(root.getLeftNode(),value));
        else if(root.getValue().charAt(0) == value.charAt(0))
            root.setMiddleNode(addNode(root.getMiddleNode(),value));
        else if(root.getValue().charAt(0) < value.charAt(0))
            root.setRightNode(addNode(root.getRightNode(),value));
        
        //return root if all else fails
        return root;
    }

    //prints the tree printPreorder recursively
    public void printPreorder(String filename){
        try(FileWriter myWriter = new FileWriter(filename   ".preorder")) {
            printPreorder(myWriter, root, 0);
            myWriter.close();
            System.out.println();
            System.out.println("Successfully wrote to the preorder file.");
        } 
        catch (IOException e) {
            System.out.println("An error occurred while writing to the preorder file.");
        }
    }
    
    //helper function for printPreorder()
    private void printPreorder(FileWriter fileWriter, Node root,int depth) {
        //base case
        if(root == null)
            return;
        //write to file and recursive cases
        try{
            String indent = "";
            for(int a = 0 ; a < depth ; a  ){
                indent  = "  ";
            }
            fileWriter.write(indent   root.getValue().charAt(0)   ":"   root.getValue()   "\n");
            System.out.println(indent   root.getValue().charAt(0)   ":"   root.getValue());
        }
        catch(IOException e){
            System.out.println("Something went wrong while writing to the .preorder file.");
            System.exit(4);
        }
        depth  ;
        printPreorder(fileWriter, root.getLeftNode(), depth);
        printPreorder(fileWriter, root.getMiddleNode(), depth);
        printPreorder(fileWriter, root.getRightNode(), depth);
    }

    //prints the tree printInorder recursively
    public void printInorder(String filename){
        try(FileWriter myWriter = new FileWriter(filename   ".inorder")) {
            printInorder(myWriter, root, 0);
            myWriter.close();
            System.out.println();
            System.out.println("Successfully wrote to the inorder file.");
        } 
        catch (IOException e) {
            System.out.println("An error occurred while writing to the inorder file.");
        }
    }
    
    //helper function for printInorder()
    private void printInorder(FileWriter fileWriter, Node root,int depth) {
        //base case
        if(root == null)
            return;
        
        //write to file and recursive cases
        int previousDepth = depth;
        depth  ;
        printInorder(fileWriter, root.getLeftNode(), depth);
        
        try{
            String indent = "";
            for(int a = 0 ; a < previousDepth ; a  ){
                indent  = "  ";
            }
            fileWriter.write(indent   root.getValue().charAt(0)   ":"   root.getValue()   "\n");
            System.out.print(indent   root.getValue().charAt(0)   ":"   root.getValue()   "\n");
        }
        catch(IOException e){
            System.out.println("Something went wrong while writing to the .preorder file.");
            System.exit(4);
        }
        
        printInorder(fileWriter, root.getMiddleNode(), depth);
        printInorder(fileWriter, root.getRightNode(), depth);
        
        depth  ;
    }

    //prints the tree printPostorder recursively
    public void printPostorder(String filename){
        try(FileWriter myWriter = new FileWriter(filename   ".postorder")) {
            printPostorder(myWriter, root, 0);
            myWriter.close();
            System.out.println();
            System.out.println("Successfully wrote to the postorder file.");
        } 
        catch (IOException e) {
            System.out.println("An error occurred while writing to the postorder file.");
        }
    }
    
    //helper function for printPostorder()
    private void printPostorder(FileWriter fileWriter, Node root,int depth) {
        //base case
        if(root == null)
            return;
        
        //write to file and recursive cases
        int previousDepth = depth;
        depth  ;
        
        printPostorder(fileWriter, root.getLeftNode(), depth);
        printPostorder(fileWriter, root.getMiddleNode(), depth);
        printPostorder(fileWriter, root.getRightNode(), depth);
        
        try{
            String indent = "";
            for(int a = 0 ; a < previousDepth ; a  ){
                indent  = "  ";
            }
            fileWriter.write(indent   root.getValue().charAt(0)   ":"   root.getValue()   "\n");
            System.out.print(indent   root.getValue().charAt(0)   ":"   root.getValue()   "\n");
        }
        catch(IOException e){
            System.out.println("Something went wrong while writing to the .postorder file.");
            System.exit(4);
        }
    }

    //getter and setter
    public void setRoot(Node node) {
        root = node;
    }

    public Node getRoot() {
        return root;
    }

}

Node:

/**
 * Sources for this class:
 * https://www.softwaretestinghelp.com/binary-search-tree-in-java/
 */
package mikefitzgibbon.p0;

public class Node {
    //this is how the tree branches out, 3 children per node
    Node left, middle, right;
    String value = "";
    
    public Node(String val) {
        value=val;
    }
    
    //getters and setters
    public void setRightNode(Node node) {
        right=node;
    }

    public void setLeftNode(Node node) {
        left=node;
    }

    public void setMiddleNode(Node node) {
        middle=node;
    }

    public Node getRightNode() {
        return right;
    }

    public Node getLeftNode() {
        return left;
    }

    public Node getMiddleNode() {
        return middle;
    }

    public String getValue() {
        return value;
    }

}

CodePudding user response:

scanner.hasNext() will block (freeze the thread) until user input happens. You might think "No, it would return false, as currently there is no token available - there is no 'next'", but that's not how it works. There indeed may not currently be a next. But who knows what happens from here on out? Perhaps the user types CTRL C, closing the pipe, thus, hasNext() will now return false, as there will never be a next token. Or, the user types something, and it will then return true, as now clearly there is a next token to be had. That's why it blocks: It cannot give a certain answer until you do something.

This may make no sense - keep in mind that 'standard input' is not the keyboard. It's... whatever stream is hooked up to standard input. By default that is a keyboard, but run your app with java -jar whatever.jar <somefile.txt and now it's that file. Which has a limited size. hasNext() would start returning false once you get to the 'end of the file'. You cannot normally ever get to the 'end of the keyboard', but the abstraction isn't designed solely for keyboards.

In other words, your code:

response = scanner.next();
while(scanner.hasNext() && ....

Will:

  1. Read a token from standard input.
  2. Will... read a token from standard input AGAIN (because hasNext() does that. If it works out, it returns true. If it turns out there is no token now nor will there ever be, it returns false).

This explain why you have to enter input twice. The solution is to remove the hasNext call there. Move it INTO the while loop. Instead of response = scanner.next() inside the loop, do something like:

  if (!scanner.hasNext()) return; // or break, or System.exit(0), or whatever you want.
  response = scanner.next();
}

CodePudding user response:

I found that putting the scanner.hasNext() at the end of the expression fixed the problem, but I'm not entirely sure how. Here is teh updated code:

//gets input from console until "exit" or "done" is typed
response = scanner.next();
while(!response.equals("exit") && !response.equals("done") && scanner.hasNext()) {
    fileWriter.write(response   " ");
    response = scanner.next();
}
  • Related