чтение данных из файла и печать только определенных экземпляров данных

#java

#java

Вопрос:

Я пытаюсь выполнить это упражнение, но мне трудно его выполнить. Входные данные сканируются из файла. Затем информация форматируется по мере ее вывода.

В настоящее время файл csv содержит следующую информацию:

 16:40,Wonders of the World,G
20:00,Wonders of the World,G
19:00,End of the Universe,NC-17
12:45,Buffalo Bill And The Indians or Sitting Bull's History Lesson,PG
15:00,Buffalo Bill And The Indians or Sitting Bull's History Lesson,PG
19:30,Buffalo Bill And The Indians or Sitting Bull's History Lesson,PG
10:00,Adventure of Lewis and Clark,PG-13
14:30,Adventure of Lewis and Clark,PG-13
19:00,Halloween,R
 

Но мой вывод выглядит так:

 Wonders of the World                         |     G | 16:40
Wonders of the World                         |     G | 20:00
End of the Universe                          | NC-17 | 19:00
Buffalo Bill And The Indians or Sitting Bull |    PG | 12:45
Buffalo Bill And The Indians or Sitting Bull |    PG | 15:00
Buffalo Bill And The Indians or Sitting Bull |    PG | 19:30
Adventure of Lewis and Clark                 | PG-13 | 10:00
Adventure of Lewis and Clark                 | PG-13 | 14:30
Halloween                                    |     R | 19:00
 

Мне нужно иметь возможность выводить только один фильм со временем показа, чтобы он выглядел так.

 Wonders of the World                         |     G | 16:40 20:00
End of the Universe                          | NC-17 | 19:00
Buffalo Bill And The Indians or Sitting Bull |    PG | 12:45 15:00 19:30
Adventure of Lewis and Clark                 | PG-13 | 10:00 14:30
Halloween                                    |     R | 19:00
 

Мой код до сих пор:

 public class LabProgram4 {

    public static void main(String[] args) throws IOException {
        String filename = "movies.csv";
        int recordCount = 0;

        Scanner fileScanner = new Scanner(new File(filename));

        while (fileScanner.hasNextLine()) {
            fileScanner.nextLine();
              recordCount;
        }

        String[] showtimes = new String[recordCount];
        String[] title = new String[recordCount];
        String[] rating = new String[recordCount];

        fileScanner.close();

        fileScanner = new Scanner(new File(filename));

        for (int i = 0; i < recordCount;   i) {
            String[] data = fileScanner.nextLine().strip().split(",");
            showtimes[i] = data[0].strip();
            title[i] = data[1].strip();
            rating[i] = data[2].strip();
        }

        fileScanner.close();

        for (int i = 0; i < recordCount;   i) {
            if (title[i].length() > 44)
                title[i] = title[i].substring(0, 44);

            System.out.printf("%-44s | %5s | %sn", title[i], rating[i], showtimes[i]);
        }
    }

}
 

Комментарии:

1. С этим было бы намного проще справиться, если бы вы создали простой класс POJO для хранения данных для фильма. И используйте что-то другое, кроме массива, чтобы вам не нужно было читать файл дважды.

2. Поскольку выходные данные должны быть отформатированы определенным образом. Сохранение элементов в массиве было первой попыткой решить проблему. Похоже, мне, возможно, придется создать другой массив, который будет принимать информацию только из текущего массива.

Ответ №1:

 public static final class Movie {

    private String title;
    private String showTime;
    private String rating;

}

public static void main(String... args) throws FileNotFoundException {
    List<Movie> movies = readMovies(new File("d:/movies.csv"));
    Map<String, List<Movie>> map = movies.stream().collect(Collectors.groupingBy(movie -> movie.title));
    print(map);
}

private static void print(Map<String, List<Movie>> map) {
    int titleWidth = getTitleWidth(map);
    int ratingWidth = getRatingWidth(map);

    map.forEach((title, movies) -> {
        String rating = movies.stream().map(movie -> movie.rating).distinct().collect(Collectors.joining(" "));
        String showTime = movies.stream().map(movie -> movie.showTime).distinct().sorted().collect(Collectors.joining(" "));
        System.out.format("%-"   titleWidth   "s | %-"   ratingWidth   "s | %sn", title, rating, showTime);
    });
}

private static int getTitleWidth(Map<String, List<Movie>> map) {
    return map.keySet().stream()
              .mapToInt(String::length)
              .max().orElse(0);
}

private static int getRatingWidth(Map<String, List<Movie>> map) {
    return map.values().stream()
              .mapToInt(movies -> movies.stream()
                                        .map(movie -> movie.rating)
                                        .distinct()
                                        .mapToInt(String::length)
                                        .sum())
              .max().orElse(0);
}

private static final int SHOW_TIME = 0;
private static final int TITLE = 1;
private static final int RATING = 2;

private static List<Movie> readMovies(File file) throws FileNotFoundException {
    try (Scanner scan = new Scanner(file)) {
        List<Movie> movies = new ArrayList<>();

        while (scan.hasNext()) {
            String[] data = scan.nextLine().split(",");
            Movie movie = new Movie();
            movie.title = data[TITLE].trim();
            movie.showTime = data[SHOW_TIME].trim();
            movie.rating = data[RATING].trim();
            movies.add(movie);
        }

        return movies;
    }
}
 

Комментарии:

1. Ответы только для кода не приветствуются, пожалуйста, добавьте некоторый контекст, описывающий ваше решение

2. Спасибо за помощь. Не могли бы вы объяснить, почему вы пошли на этот подход? Я должен признать, что это то, чему я все еще учусь, поэтому то, как я это делал, было больше основано на том, как нас учат.

Ответ №2:

На мой взгляд, чтение файла один раз с единственной целью получения количества записей (скажем) — это просто неправильный путь. Прочитайте файл один раз и выполняйте задачу по мере чтения файла.

Существует множество способов чтения файла и сохранения или отображения записей уникальным способом (например, без дублирования заголовков). Я полагаю, что использование параллельных массивов для хранения данных — это один из способов, но эти массивы должны быть инициализированы до определенной длины, поскольку они не могут расти динамически. Хотя это и не невозможно, в данной конкретной ситуации это довольно проблематично и потребует гораздо больше кода для выполнения задачи по сравнению с использованием объекта коллекции, такого как интерфейс списка, ArrayList, (и т.д.), Который может динамически расти.

Приведенный ниже код использует java.util.Интерфейс списка для хранения, а затем последующего отображения фильмов, считанных из movies.csv файла. Код выглядит длинным, но в основном это комментарии, объясняющие вещи. Я бы посоветовал вам прочитать эти комментарии и, если хотите, удалить их, поскольку они являются чрезмерными:

 // The Movies data file name.
String filename = "movies.csv";
// Counter to keep track of the number of movies stored.
int moviesCount = 0;

// List Interface object to store movie titles in.
java.util.List<String> movies = new java.util.ArrayList<>();
    
// 'Try With Resources' used here to auto-close the reader
try (Scanner reader = new Scanner(new File(filename))) {
    // Read the data file line by line.
    String dataLine;
    while (reader.hasNextLine()) {
        dataLine = reader.nextLine().trim();
        // Skip blank lines...
        if (dataLine.isEmpty()) {
            continue;
        }
        /* The regex ("\s*,\s*") passed to the String#split() method 
           below handles  any number of whitespaces before or after the 
           comma delimiter on any read in data file line.  */
        String[] data = dataLine.split("\s*,\s*"); 
        /* Do we already have title in the 'movies' List?
           If so just add the show-time to the title and
           continue on to the next file data line.       */
        boolean alreadyHave = false;  // Flag that we don't already have this title
        for (int i = 0; i < movies.size(); i  ) {
            // Do we already have the movie title in the list?
            if (movies.get(i).contains(data[1])) {
                // Yes we do so flag it that we already do have this title.
                alreadyHave = true;
                // Add the additional show-time to that title's stored information
                movies.set(i, movies.get(i)   " "   data[0]);
                /* Break out of this 'for' loop since there is no 
                   more need to check other titles in the List. */
                break; 
            }
        }
        /* If however we don't already have this movie title 
           in the List then add it in the desired display 
           format using the Pipe (|) character as a delimiter. */
        if (!alreadyHave) {
            moviesCount  ;  // Increment the number of movies 
            movies.add(String.format("%s | %s | %s", data[1], data[2], data[0]));
        }
    }
}

// DISPLAY THE MOVIES LIST IN TABLE STYLE FASHION
// Display Title in Console Window:
String msg = "There are "   moviesCount   " movies with various show-times:";
System.out.println(msg);  // Print title
// Display Table Header:
String header = String.format("%-44s | %6s | %s", "Movie Title", "Rating", "Show Times");
String overUnderline = String.join("", java.util.Collections.nCopies(header.length(), "="));
// Header Overline
System.out.println(overUnderline);
System.out.println(header);
// Header Underline
System.out.println(overUnderline);
// Display the movies in console window.
for (String movie : movies) {
    /* Split the current List element into its respective parts
       using the String#split() method so that the List contents
       can be displayed in a table format. The regex passed t0 
       the 'split()' method ("\s*\|\s*") will take care of any
       whitespaces before or after any Pipe (|) delimiter so that 
       String#trim() or String#strip() is not required. Note that
       the Pipe (|) delimiter needs to be escaped (\|) within the
       expression so as to acquire is literal meaning since it is 
       a regex meta character.                          */
    String[] movieParts = movie.split("\s*\|\s*");
    /* 'Ternary Operators' are used in the String#format() data
       section components so to truncate movie Title Names to the 
       desire table cell width of 44 and to convert Rating and
       Show-Times to "N/A" should they EVER be empty (contain no
       data).            */
    System.out.println(String.format("%-44s | %6s | %s", 
                                     (movieParts[0].length() > 44 ? movieParts[0].substring(0, 44) : movieParts[0]), 
                                     (movieParts[1].isEmpty() ? "N/A" : movieParts[1]), 
                                     (movieParts[2].isEmpty() ? "N/A" : movieParts[2])));
}
System.out.println(overUnderline);
 

Если файл данных действительно содержит то, что вы указали в своем сообщении, то приведенный выше код отобразит следующее в окне консоли:

 There are 5 movies with various show-times:
==================================================================
Movie Title                                  | Rating | Show Times
==================================================================
Wonders of the World                         |      G | 16:40 20:00
End of the Universe                          |  NC-17 | 19:00
Buffalo Bill And The Indians or Sitting Bull |     PG | 12:45 15:00 19:30
Adventure of Lewis and Clark                 |  PG-13 | 10:00 14:30
Halloween                                    |      R | 19:00
==================================================================