5
\$\begingroup\$

I'm currently following Java moocfi Helsinki course.

This is my current assignment :


In this exercise we are going to create a program that allows for searching for recipes based on their name, cooking time, or the name of an ingredient. The program should read the recipes from a file that the user provides. It might be a good idea to brush up on reading information from files (part 4) before beginning.

Each recipe consists of three or more rows in a recipe file. The first row is for the name of the recipe, the second the cooking time (an integer), and the third and possibly following rows list the ingredients used in the recipe. An empty row follows the last ingredient row. There can be many recipes in a single file. Below, an example file containing recipes is described.

  1. Reading and listing recipes: First create the functionality to read and list recipes. The user interface of the program is described below. You may assume that the user only enters files that exist. Below we assume that the example recipes given earlier in the exercise description are stored in the file recipes.txt
  2. Finding recipes by name: Make it possible to find recipes by their names. Finding by name is done with the command find name, after which the user is asked for the name that is used to search. The search should print all the recipes whose names contain the string given by the user.
  3. Searching for recipes by cooking time: Next, implement the possibility to find recipes based on their cooking time. This is done with the command find cooking time, after which the user is asked for the longest acceptable cooking time. The program should react by printing all the recipes whose cooking times don't exceed the cooking time given by the user (so equal or less time).
  4. Finding recipes based on their ingredients: Finally, add the functionality to search for recipes based on their ingredients. This is done by choosing the command find ingredient, after which the user is asked for a string. The program should then print all the recipes that contain the specified string. Notice that with this option the given string must match exactly the ingredient that is searched for (e.g. "ugar" will return different results than "sugar").

This is my solution. I already have a tiny background in Java, but I'm trying to improve at separating the program logic from the user interface. Any suggestions will be appreciated.


recipes.txt

Pancake dough
60
milk
egg
flour
salt
butter

Meatballs
20
ground meat
egg
breadcrumbs

Tofu rolls
30
tofu
rice
water
carrot
cucumber
avocado
wasabi

RecipeSearch.java

import java.util.Scanner;
 
public class RecipeSearch {
 
    public static void main(String[] args) {
 
        Scanner scanner = new Scanner(System.in);
        RecipesCollection recipesCollection = new RecipesCollection();
        UserInterface ui = new UserInterface(scanner, recipesCollection);
        ui.start();
    }
}

UserInterface.java

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
 
public class UserInterface {
 
    private final static List<String> USAGE;
 
    static {
        final String[] list = new String[]{
                "list - lists the recipes",
                "stop - stops the program",
                "find name - searches recipes by name",
                "find cooking time - searches recipes by cooking time",
                "find ingredient - searches recipes by ingredient"
        };
        USAGE = Collections.unmodifiableList(Arrays.asList(list));
    }
 
    private Scanner scanner;
    private RecipesCollection recipesCollection;
 
    public UserInterface(Scanner scanner, RecipesCollection recipesCollection) {
        this.scanner = scanner;
        this.recipesCollection = recipesCollection;
    }
 
    public void start() {
        System.out.print("File to read:");
        String fileName = scanner.nextLine();
        recipesCollection.fillCollectionFromFile(fileName);
        USAGE.forEach(System.out::println);
        while (true) {
            System.out.print("Enter command: ");
            String command = scanner.nextLine();
            if (command.equals("stop")) {
                break;
            } else if (command.equals("list")) {
                this.recipesCollection.listRecipes();
            } else if (command.equals("find name")) {
                System.out.print("Searched word:");
                String name = scanner.nextLine();
                this.recipesCollection.findByName(name);
            } else if (command.equals("find cooking time")) {
                System.out.print("Max cooking time:");
                int time = Integer.parseInt(scanner.nextLine());
                this.recipesCollection.findByCookingTime(time);
            } else if (command.equals("find ingredient")) {
                System.out.print("Ingredient:");
                String ingredient = scanner.nextLine();
                this.recipesCollection.findByIngredient(ingredient);
            }
        }
    }
}

Recipe.java

import java.util.List;
 
public class Recipe {
    private final String name;
    private final int preparationTime;
    private final List<String> ingredients;
 
    public Recipe(String name, int preparationTime, List<String> ingredients) {
        this.name = name;
        this.preparationTime = preparationTime;
        this.ingredients = ingredients;
    }
 
    public String getName() {
        return name;
    }
 
    public int getPreparationTime() {
        return preparationTime;
    }
 
    public List<String> getIngredients() {
        return ingredients;
    }
 
    @Override
    public String toString() {
        return name + ", cooking time: " + preparationTime;
    }
}

RecipesCollection.java

import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
 
public class RecipesCollection {
    private List<Recipe> recipes;
 
    public RecipesCollection() {
        this.recipes = new ArrayList<>();
    }
 
    public void fillCollectionFromFile(String fileName) {
        try (Scanner scanner = new Scanner(Paths.get(fileName))) {
            while (scanner.hasNextLine()) {
                String recipeName = scanner.nextLine();
                int preparationTime = Integer.parseInt(scanner.nextLine());
                ArrayList<String> ingredients = new ArrayList<>();
                while (scanner.hasNextLine()) {
                    String ingredient = scanner.nextLine();
                    if (ingredient.isEmpty()) {
                        break;
                    }
                    ingredients.add(ingredient);
                }
                Recipe recipe = new Recipe(recipeName, preparationTime, ingredients);
                this.recipes.add(recipe);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
 
    public void listRecipes() {
        System.out.println("Recipes:");
        this.recipes.forEach(System.out::println);
    }
 
    public void findByName(String name) {
        System.out.println("Recipes:");
        ArrayList<Recipe> searchResult = new ArrayList<>();
        for (Recipe recipe :
                this.recipes) {
            if (recipe.getName().contains(name)) {
                searchResult.add(recipe);
            }
        }
        searchResult.forEach(System.out::println);
    }
 
    public void findByCookingTime(int time) {
        System.out.println("Recipes:");
        ArrayList<Recipe> searchResult = new ArrayList<>();
        for (Recipe recipe :
                this.recipes) {
            if (recipe.getPreparationTime() <= time) {
                searchResult.add(recipe);
            }
        }
        searchResult.forEach(System.out::println);
    }
 
    public void findByIngredient(String ingredient) {
        System.out.println("Recipes:");
        ArrayList<Recipe> searchResult = new ArrayList<>();
        for (Recipe recipe :
                this.recipes) {
            if (recipe.getIngredients().contains(ingredient)) {
                searchResult.add(recipe);
            }
        }
        searchResult.forEach(System.out::println);
    }
}
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Almost perfect, with one big exception:

The findBy... methods in RecipesCollection shouldn't be writing to System.out. That breaks the separation of program logic from the user interface which you have done very nicely otherwise. Instead have those methods simply return a List<Recipe>.

Other than that there are a few places could be simpified by using other techniques, such as Streams. For example, reading the file with Files.lines() or filtering the recipies. E.g.:

public List<Recipe> findByName(String name) {
    // toList() requires Java 16. Otherwise use `.collect(Collections.toList())`
    return this.recipes.stream().filter(r -> r.getName().contains(name)).toList();
}

Oh, and initializing the menu list could be done with Java 9's List.of:

private final static List<String> USAGE = List.of(
    "list - lists the recipes",
    "stop - stops the program"
    // etc.
);
\$\endgroup\$
-3
\$\begingroup\$

Recipe

import java.util.List;
import java.util.Objects;

public record Recipe(String name, int preparationTime, List<String> ingredients) {


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Recipe recipe = (Recipe) o;
        return preparationTime == recipe.preparationTime && Objects.equals(name, recipe.name) && Objects.equals(ingredients, recipe.ingredients);
    }
}

RecipeSearch

import java.util.Scanner;

public class RecipeSearch {
    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        RecipesCollection recipesCollection = new RecipesCollection();
        UserInterface ui = new UserInterface(scanner, recipesCollection);
        ui.start();
    }
}

RecipesCollection

import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class RecipesCollection {
    private List<Recipe> recipes;

    public RecipesCollection() {
        this.recipes = new ArrayList<>();
    }

    public void fillCollectionFromFile(String fileName) {
        try (Scanner scanner = new Scanner(Paths.get(fileName))) {
            while (scanner.hasNextLine()) {
                String recipeName = scanner.nextLine();
                int preparationTime = Integer.parseInt(scanner.nextLine());
                ArrayList<String> ingredients = new ArrayList<>();
                while (scanner.hasNextLine()) {
                    String ingredient = scanner.nextLine();
                    if (ingredient.isEmpty()) {
                        break;
                    }
                    ingredients.add(ingredient);
                }
                Recipe recipe = new Recipe(recipeName, preparationTime, ingredients);
                this.recipes.add(recipe);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void listRecipes() {
        System.out.println("Recipes:");
        this.recipes.forEach(System.out::println);
    }

    public List<Recipe> findByName(String name) {
        ArrayList<Recipe> searchResult = new ArrayList<>();
        for (Recipe recipe :
            this.recipes) {
            if (recipe.name().contains(name)) {
                searchResult.add(recipe);
            }
        }
        return searchResult;
    }

    public List<Recipe> findByCookingTime(int time) {
        ArrayList<Recipe> searchResult = new ArrayList<>();
        for (Recipe recipe :
            this.recipes) {
            if (recipe.preparationTime() <= time) {
                searchResult.add(recipe);
            }
        }
        return searchResult;
    }

    public List<Recipe> findByIngredient(String ingredient) {
        ArrayList<Recipe> searchResult = new ArrayList<>();
        for (Recipe recipe :
            this.recipes) {
            if (recipe.ingredients().contains(ingredient)) {
                searchResult.add(recipe);
            }
        }
        return searchResult;
    }
}

UserInterface

import java.util.List;
import java.util.Scanner;

public class UserInterface {

private final static List<String> USAGE;

static {
    final String[] list = new String[]{
        "list - lists the recipes",
        "stop - stops the program",
        "help - show commands",
        "find name - searches recipes by name",
        "find cooking time - searches recipes by cooking time",
        "find ingredient - searches recipes by ingredient"
    };
    USAGE = List.of(list);
}

private Scanner scanner;
private RecipesCollection recipesCollection;

public UserInterface(Scanner scanner, RecipesCollection recipesCollection) {
    this.scanner = scanner;
    this.recipesCollection = recipesCollection;
}

public void start() {
    System.out.print("File to read:");
    String fileName = scanner.nextLine();
    recipesCollection.fillCollectionFromFile(fileName);
    USAGE.forEach(System.out::println);
    String command = "";
    do {
        System.out.print("Enter command: ");
        command = scanner.nextLine();
        switch (command) {
            case "help" -> USAGE.forEach(System.out::println);
            case "list" -> this.recipesCollection.listRecipes();
            case "find name" -> {
                System.out.print("Searched word:");
                String name = scanner.nextLine();
                System.out.println("Recipes:");
                this.recipesCollection.findByName(name).forEach(System.out::println);
            }
            case "find cooking time" -> {
                System.out.print("Max cooking time:");
                int time = Integer.parseInt(scanner.nextLine());
                System.out.println("Recipes:");
                this.recipesCollection.findByCookingTime(time).forEach(System.out::println);
            }
            case "find ingredient" -> {
                System.out.print("Ingredient:");
                String ingredient = scanner.nextLine();
                System.out.println("Recipes:");
                this.recipesCollection.findByIngredient(ingredient).forEach(System.out::println);
            }
        }
    } while (!command.equals("stop"));
}

}

\$\endgroup\$
2
  • 1
    \$\begingroup\$ You should post the review as the content of your answer. A link to an answer is not an answer. \$\endgroup\$ Mar 15 at 8:12
  • \$\begingroup\$ Thank you, you confirmed some of my doubts and used the solution I was thinking of. \$\endgroup\$
    – FakiB
    Mar 15 at 12:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.