Handle csv file with almost similar records but different times - need to group them as one record

Viewed 205

I am attempting to resolve the below lab and having issues. This problem involves a csv input. There is criteria that the solution needs to meet. Any help or tips at all would be appreciated. My code is at the end of the problem along with my output.

Each row contains the title, rating, and all showtimes of a unique movie.
A space is placed before and after each vertical separator ('|') in each row.
Column 1 displays the movie titles and is left justified with a minimum of 44 characters.
If the movie title has more than 44 characters, output the first 44 characters only.
Column 2 displays the movie ratings and is right justified with a minimum of 5 characters.
Column 3 displays all the showtimes of the same movie, separated by a space.

This is the input:

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

This is the expected output:

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

My code so far:

import csv
rawMovies = input()
repeatList = []

with open(rawMovies, 'r') as movies:
    moviesList = csv.reader(movies)
    for movie in moviesList:
        time = movie[0]
        #print(time)
        show = movie[1]
        if len(show) > 45:
            show = show[0:44]
        #print(show)
        rating = movie[2]
        #print(rating)
        print('{0: <44} | {1: <6} | {2}'.format(show, rating, time))

My output doesn't have the rating aligned to the right and I have no idea how to filter for repeated movies without removing the time portion of the list:

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
4 Answers

You could collect the input data in a dictionary, with the title-rating-tuples as keys and the showtimes collected in a list, and then print the consolidated information. For example (you have to adjust the filename):

import csv

movies = {}
with open("data.csv", "r") as file:
    for showtime, title, rating in csv.reader(file):
        movies.setdefault((title, rating), []).append(showtime)
for (title, rating), showtimes in movies.items():
    print(f"{title[:44]: <44} | {rating: >5} | {' '.join(showtimes)}")

Output:

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

Since the input seems to come in connected blocks you could also use itertools.groupby (from the standard library) and print while reading:

import csv
from itertools import groupby
from operator import itemgetter

with open("data.csv", "r") as file:
    for (title, rating), group in groupby(
        csv.reader(file), key=itemgetter(1, 2)
    ):
        showtimes = " ".join(time for time, *_ in group)
        print(f"{title[:44]: <44} | {rating: >5} | {showtimes}")

For this consider the max length of the rating string. Subtract the length of the rating from that value. Make a string of spaces of that length and append the rating. so basically

your_desired_str = ' '*(6-len(Rating))+Rating

also just replace

'somestr {value}'.format(value)

with f strings, much easier to read

f'somestr {value}'

I would use Python's groupby() function for this which helps you to group consecutive rows with the same value.

For example:


import csv
from itertools import groupby

with open('movies.csv') as f_movies:
    csv_movies = csv.reader(f_movies)
    
    for title, entries in groupby(csv_movies, key=lambda x: x[1]):
        movies = list(entries)
        showtimes = ' '.join(row[0] for row in movies)
        rating = movies[0][2]
        
        print(f"{title[:44]: <44} | {rating: >5} | {showtimes}")

Giving you:

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

So how does groupby() work?

When reading a CSV file you will get a row at a time. What groupby() does is to group rows together into mini-lists containing rows which have the same value. The value it looks for is given using the key parameter. In this case the lambda function is passed a row at a time and it returns the current value of x[1] which is the title. groupby() keeps reading rows until that value changes. It then returns the current list as entries as an iterator.

This approach does assume that the rows you wish to group are in consecutive rows in the file. You could even write you own kind of group by generator function:

def group_by_title(csv):
    title = None
    entries = []
    
    for row in csv:
        if title and row[1] != title:
            yield title, entries
            entries = []
        
        title = row[1]
        entries.append(row)
    
    if entries:
        yield title, entries


with open('movies.csv') as f_movies:
    csv_movies = csv.reader(f_movies)
    
    for title, entries in group_by_title(csv_movies):
        showtimes = ' '.join(row[0] for row in entries)
        rating = entries[0][2]
        
        print(f"{title[:44]: <44} | {rating: >5} | {showtimes}")

Below is what I ended up with after some tips from the community.

rawMovies = input()
outputList = []

with open(rawMovies, 'r') as movies:
    moviesList = csv.reader(movies)
    movieold = [' ', ' ', ' ']
    for movie in moviesList:
        if movieold[1] == movie[1]:
            outputList[-1][2] += ' ' + movie[0]
        else:
            time = movie[0]
            # print(time)
            show = movie[1]
            if len(show) > 45:
                show = show[0:44]
            # print(show)
            rating = movie[2]
            outputList.append([show, rating, time])
            movieold = movie
            # print(rating)
#print(outputList)

for movie in outputList:
    print('{0: <44} | {1: <5} | {2}'.format(movie[0], movie[1].rjust(5), movie[2]))
Related