javafilearraylistwhile-loop

How to make this while loop create a list of objects?


The following code is meant to loop through a txt file and create a list of recipes that each have a name, cooking time and ingredients, which are themselves put on a list

 try (Scanner scanner = new Scanner(Paths.get("recipes.txt"))) {

            while (scanner.hasNextLine()) {
                
                name = scanner.nextLine();
                cookingTime = Integer.valueOf(scanner.nextLine());         

                // while (!(scanner.nextLine().isEmpty())) {
                //    ingredient = scanner.nextLine();
                //    ingredients.add(ingredient);
                // }
                while (true) {
                    checkEmpty = scanner.nextLine();
                    if (checkEmpty.isEmpty()) {
                        break;
                    } else {
                        ingredient = checkEmpty;
                    }
                    ingredients.add(ingredient);
                }
                
                recipes.add(new Recipe(name, cookingTime, ingredients));



            }

        } catch (Exception e) {
           System.out.println("Error: " + e.getMessage());

This should work with any txt file in this format but in particular this is the example file (the code I used initially has the while loop that I now put in // comment, I updated the code based on a reply, but unfortunately that did not solve my problem) Update: My problem has been identified and fixed, thanks to everyone taking the time to help me!

Pancake dough
60
milk
egg
flour
sugar
salt
butter

Meatballs
20
ground meat
egg
breadcrumbs

Tofu rolls
30
tofu
rice
water
carrot
cucumber
avocado
wasabi

The problem I have pertains specifically to the list of the ingredients and I strongly suspect the condition of the inner while loop is responsible for the problem.

So what happens is the list only takes in every other ingredient for the fist recipe. So egg, sugar, butter in the example. Once the loop comes around to the second recipe my overarching loop is interrupted and my catch informs me of an error: no line found.

My question can essentially be boiled down to:

** what does the condition of the inner while loop have to be for my program to work as intended? **

I have tried several things but the outcome is essentially that suggesting to the loop to stop once the next line is empty always produces the erroneous output I described, and other than that I simply have no idea how to control the loop to stop once the last ingredient is added and a new recipe has to be created.

As a quick PSA: I picked up programming only a couple of weeks ago and this is actually one of the final coding exercises of the Helsinki Java Moocfi that taught me what I know thus far about coding. So please be kind and consider my very low level of expertise, I suppose the answer to my question is probably very obvious and easy but unfortunately I am unable to figure out how to do this on my own so I would appreciate any and all help. Thanks in advance!


Solution

  • There are a few problems with your code:

    1:

                cookingTime = Integer.valueOf(scanner.nextLine());         
    

    This first one is not really a problem, but a style/efficiency issue. valueOf returns an Integer, which is an object that wraps an int. Unless you actually want such a thing, it's better to call parseInt instead, which returns an int directly.

    2:

                while (!(scanner.nextLine().isEmpty())) {
    

    As many people have already pointed out, this is why you're skipping every other line. Once you've done !(scanner.nextLine().isEmpty()), that line has already been read from the file, and is now gone; you've lost it and can't get it back to see what it was that you've just read. There are many ways to do fix this; for example, as Asmir said in his answer:

    String ingredient = scanner.nextLine();
    while (!ingredient.isEmpty() && scanner.hasNextLine()) {
        ingredients.add(ingredient);
        ingredient = scanner.nextLine();
    }
    

    Another way, similar to what you said you tried, is the following. Unlike the above, it avoids having to call nextLine() in two separate places.

    while (scanner.hasNextLine()) {
        String ingredient = scanner.nextLine();
        if (ingredient.isEmpty()) break;
        ingredients.add(ingredient);
    }
    

    And a third way is as follows. This one avoids using the break statement which some people say goes against the idea of structured programming:

    String ingredient;
    while (scanner.hasNextLine() && !(ingredient = scanner.nextLine()).isEmpty()) {
        ingredients.add(ingredient);
    }
    

    There is no consensus about which of the above three ways is to be preferred. Each has its pros and cons.

    3:

                    ingredients.add(ingredient);
    

    The problem here is that you are using the same ingredients list for the whole program, so all your recipes will have the same very long list of ingredients. You need to create a new list of ingredients for each recipe, and the normal way to do that is by declaring and initialising the list just before the inner loop:

    while (scanner.hasNextLine()) {
        ...
        List<String> ingredients = new ArrayList<>();
        while (...) {
            ...
            ingredients.add(ingredient);
        }
        recipes.add(new Recipe(..., ingredients));
    }
    

    4:

    } catch (Exception e) {
        System.out.println("Error: " + e.getMessage());
    }
    

    Sometimes, it's better not to catch an exception. Just leave out the catch block altogether.

    If you don't catch an exception, it will bubble up to the caller, and then to the caller's caller, until it finds a catch. If there isn't one, it will print a stack trace and terminate the application. Sometimes, that is exactly what you want. A stack trace shows you exactly where the problem occurred, and is great for debugging. On the other hand, if you want to print an error message to the user (who could be a customer, etc), then it's better not to confuse them with a stack trace, in which case it's better to just print a simple error message.