javafile-iotext-files

Having trouble using a loop to find a user-input character from a text file in Java (Solved)


I'm having trouble writing a program for my Java class. The assignment's instructions are as follows:

"File Letter Counter:
Write a program that asks the user to enter the name of a file, and then asks the user to enter a character. The program should count and display the number of times that the specified character appears in the file. Use Notepad or another text editor to create a sample file that can be used to test the program."

The text file my professor provided us to test this program has 1307 lines of randomly cased and placed letters that this program has to go through and, for whatever reason, I can't seem to get this program to work right. I've tried using things outside of what we've learned so far in class and in the book, but I'm beyond lost.

Sample input:

f
d
s
h
j

Here's my code so far (compiled in NetBeans 23):

package filelettercounter;

import java.util.Scanner;
import java.io.*;

public class FileLetterCounter {

    public static void main(String[] args) throws IOException {

        Scanner keyboard = new Scanner(System.in);
        
        System.out.print("Enter the file name: ");
        String filename = keyboard.nextLine();
        
        File file = new File(filename);
        Scanner inputFile = new Scanner(file);
        
        /*
        My dad, after looking at my code, said (and I'm paraphrasing)
        it's basically doing a JD Vance. Is there a line? "Okay, good.
        It might not even be checking for other lines, it might be
        repeatedly iterating over the same line.
        
        I almost died because of his meme comparison.
        */
        
        do {
            
            int counter = 0;
            String line = inputFile.nextLine();
            
            System.out.print("Enter a character: ");
            String character = inputFile.nextLine();
            
            if(line.contains(character)) {
                counter++;
            }
            
            System.out.println("The character " + character + " appears " +
                    counter + " times in the file.");
            
            }while(inputFile.hasNext());
        
        inputFile.close();
        
        }
    }

The output prints all 1307 lines showing that they each appear 0 times...

   Enter a character: The character f appears 0 times in the file.
   Enter a character: The character d appears 0 times in the file.
   Enter a character: The character s appears 0 times in the file.
   Enter a character: The character h appears 0 times in the file.
   Enter a character: The character j appears 0 times in the file.

...with an exception thrown most of the way down the output.

Exception in thread "main" java.util.NoSuchElementException: No line found
    at java.base/java.util.Scanner.nextLine(Scanner.java:1660)
    at filelettercounter.FileLetterCounter.main(FileLetterCounter.java:31)

The output I need to achieve is something like this:

Enter the file name: "filename"
Enter a character: "character"
The character (character) appears (number of times) times in the file.

Regarding input from any of you who are infinitely more experienced than I am, I'm not supposed to be doing anything fancy. The farthest we've gotten through as a class has been the various loops (for, if/else if/else, do-while, while), accumulating variables, etc. It is as basic as basic gets. So, please no lists or arrays or delimiters, or anything crazy like that.

Edit:

I solved my problem and got the program working. If, for any reason, someone in the future comes along and needs a solution like this, I'll post the code that I have that now works as intended (with some added comments):

package filelettercounter;

import java.util.Scanner;
import java.io.*;

public class FileLetterCounter {

    public static void main(String[] args) throws IOException, FileNotFoundException{
        File file;
        Scanner fileScanner;
        String filename;
        Scanner keyboard = new Scanner(System.in);
        
        System.out.print("Enter file name: ");
        filename = keyboard.nextLine();

        //I needed to store the character value BEFORE
                            //switching to the File Scanner.        
        System.out.print("Enter a character: ");
        String character = keyboard.nextLine();
        
        file = new File(filename);
        
        if(!file.exists()) {
            System.out.println("The file does not exist.");
            System.exit(0);
        }
        
        fileScanner = new Scanner(file);
        
        if(!fileScanner.hasNextInt()) {
            System.out.println("Error with file structure.");
            System.exit(0);
        }
        
        /*
        I didn't need the do-while loop, either. At the time of
        the original code, I was throwing things into the code to
        see what would work and crossing my fingers.

        It took me a while to figure this part out, as well.
        I didn't think having an int variable and a separate
        String variable would work as intended, but it worked
                (don't ask me why I thought this, I just did).
        */
        int lines = fileScanner.nextInt();
        fileScanner.nextLine();
        
        int counter = 0;
        
        for(int i = 0; i < lines; i++) {

            String line = fileScanner.nextLine();
            
            if(line.contains(character) && character.equalsIgnoreCase(character)) {
                counter++;
            }
        }
        
        System.out.println("The character " + character + " appears " + counter
        + " times.");
        }
    }

I appreciate everyone's input; it did help.


Solution

  • Understanding the exception

    Let's start with the error;

    It's decently descriptive with No line found. Your code at FileLetterCounter.java:31 is probably not the same line in your example, I'll assume it points to the inputFile.nextLine(); line.

    This line is the place the issue is happening in your code so it's a good idea to start there.

    Next we see the method call that is throwing within your code, Scanner.nextLine(). The docs for nextLine state the following:

    public String nextLine()

    Advances this scanner past the current line and returns the input that was skipped. This method returns the rest of the current line, excluding any line separator at the end. The position is set to the beginning of the next line.

    Since this method continues to search through the input looking for a line separator, it may buffer all of the input searching for the line to skip if no line separators are present.

    Returns: the line that was skipped

    Throws:

    • NoSuchElementException - if no line was found
    • IllegalStateException - if this scanner is closed

    Since you're seeing this error, maybe the input really doesn't have any line to be found?

    Solution

    Let's go over the pseudo-code:

    get the file and input streams ready.
    loop (at least once) while the scanner has another token in its input:
        ...
    

    At this point, you should check everything is working. Maybe start by printing out each token with Scanner.next(). Now continuing with the pseudo-code:

    loop (at least once) while the scanner has another token in its input:
        set a counter to zero.
        advances scanner past the current line and returns the input
        prompt the user for a character.
        if the collected line contains an instance of the character,
            then increment counter by one.
        print out the counter.
    end loop and start again using the same scanner.
    

    Here's a few questions that may help:

    1. If we only increment the counter by 1 before printing the results, why would it be anything other than 0 or 1?
    2. If we only advance our scanner forward through the lines, what would happen to the count if some of the characters are only found in previous lines?
    3. What happens if the user keeps prompting multiple times, would we run out of lines?
    4. Why are we looping over lines? Would it be any different if we just looped over characters? Who knows, maybe the user wants to count the number of new-line characters.
    5. Why is the outer loop checking if the scanner has more tokens with hasNext() but inside the loop the entire line is consumed with nextLine()?

    So in short, there are several logic errors here. The first one you're asking about is the use of the scanner. You've got another with the counter, and another potential one with how the loop is structured.

    If you answer those questions and talk through some new pseudo code you should have a working solution.

    Extra take-aways

    When learning to coding, run often. Add 3-5 lines, maybe a println to debug, test it, and repeat.

    Pseudo code is your friend. In this case, there are logic errors in a few places that would be caught by talking through what you want to do then validating the program is doing what you expect.