Search code examples
javafile-iotext-files

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


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);
        
        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.


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.