2
\$\begingroup\$

I want to create a script that displays the rating of movie directors for a user on IMDB.

After some research, I realized that there is no free API for making such a list.

The only option is to export a list of users rating for every movie, TV show etc.

The initial list is in CSV, in the script. I make some calculations and write the list to file in XLSX format.

I've been working with Python for 2-3 years now.

I'm trying to follow 100 symbols per line convention. I'm following the 4-space indent convention and it looks much clearer.

Using Java 8.

Sample input - https://yadi.sk/d/pHZA_-jBo1TK6A Sample output - https://yadi.sk/i/sXUk8VaH7c3HAg

package directors;


import java.awt.Desktop;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;

import com.opencsv.CSVReader;
import com.opencsv.exceptions.CsvValidationException;


class App {
    private static final List<String> XLS_COLUMNS =
            Stream.of("director", "adjustedRating", "count", "ratingsSum", "movies", "averageRating")
            .collect(Collectors.toList());

    private static final List<String> KEYS =
            Stream.of("myRating", "title", "titleType", "directorsName").collect(Collectors.toList());

    private static final List<Integer> VALUES = Stream.of(1, 3, 5, 12).collect(Collectors.toList());
    private static final Map<String, Integer> CSV_COLUMNS = createMap(KEYS, VALUES);

    private static final String ENCODING = "cp1252",
                                INPUT_FILENAME = "files/ratings.csv",
                                MOVIE_TITLE_TYPE = "movie",
                                OUTPUT_FILENAME = "workbook.xlsx",
                                SHEET_NAME = "new sheet",
                                SORT_BY = "adjustedRating";

    private static final Integer NUMBER_OF_HEADER_ROWS = 1;
    private static final Double COEFFICIENT_ONE = 2.0;
    private static final Double COEFFICIENT_TWO = 4.0;
    private static final int[] COLUMN_WIDTHS = {40*256, 15*256, 15*256, 15*256, 50*256, 15*256};

    private static final Map<String, Integer> createMap(List<String> keys, List<Integer> values) {
        Map<String, Integer> map = new HashMap<>();
        Integer count = 0;

        for (String key : keys) {
            map.put(key, values.get(count));
            count += 1;
        }

        return Collections.unmodifiableMap(map);
    }

    public static Map<String, Map<String, Object>> parseCsvToMap() throws FileNotFoundException, 
                                                                          UnsupportedEncodingException,
                                                                          CsvValidationException,
                                                                          IOException {
            
        CSVReader csvFile =
                  new CSVReader(new InputStreamReader(new FileInputStream(INPUT_FILENAME), ENCODING));

        Map<String, Map<String, Object>> directorsRating = new TreeMap<String, Map<String, Object>>();

        String [] nextLine;
        while ((nextLine = csvFile.readNext()) != null) {
            Map<String, Object> innerMap = new LinkedHashMap<String, Object>();

            if (nextLine[CSV_COLUMNS.get("titleType")].equals(MOVIE_TITLE_TYPE)) {
                String directorsName = nextLine[CSV_COLUMNS.get("directorsName")];

                if (directorsRating.containsKey(directorsName)) {

                    innerMap = directorsRating.get(directorsName);

                    Integer numberOfMovies  = (Integer) innerMap.get(XLS_COLUMNS.get(2));
                    numberOfMovies++;
                    innerMap.put(XLS_COLUMNS.get(2), numberOfMovies);

                    Integer ratingsSum  = (Integer) innerMap.get(XLS_COLUMNS.get(3));
                    ratingsSum += Integer.parseInt(nextLine[CSV_COLUMNS.get("myRating")]);
                    innerMap.put(XLS_COLUMNS.get(3), ratingsSum);

                    List<String> movies = (List<String>) innerMap.get(XLS_COLUMNS.get(4));
                    movies.add(nextLine[CSV_COLUMNS.get("title")]);
                    innerMap.put(XLS_COLUMNS.get(4), movies);

                    directorsRating.put(directorsName, innerMap);

                } else {
                    innerMap.put(XLS_COLUMNS.get(1), 0.0);
                    innerMap.put(XLS_COLUMNS.get(2), 1);
                    innerMap.put(XLS_COLUMNS.get(3),
                                 Integer.parseInt(nextLine[CSV_COLUMNS.get("myRating")]));

                    List<String> movies = new ArrayList<String>();
                    movies.add(nextLine[CSV_COLUMNS.get("title")]);
                    innerMap.put(XLS_COLUMNS.get(4), movies);
                    innerMap.put(XLS_COLUMNS.get(5), 0.0);

                    directorsRating.put(directorsName, innerMap);
                }
            }
        }

        return directorsRating;
        }

    public static void calculateRatings(Map<String, Map<String, Object>> directorsRating) {
        Iterator<Map.Entry<String, Map<String, Object>>> iterator =
                directorsRating.entrySet().iterator();

        while (iterator.hasNext()) {
            Map.Entry<String, Map<String, Object>> entry = iterator.next();
            Map<String, Object> value = entry.getValue();

            Integer ratingsSum = (Integer) value.get("ratingsSum");
            Double count = ((Number) value.get("count")).doubleValue();
            Double averageRating = ratingsSum / count;
            Double adjustedRating;
            Double coefficient;

            if (count > 2) {
                coefficient = COEFFICIENT_ONE;
                if (count > 4) {
                    coefficient = COEFFICIENT_TWO;
                }
                Double adjustment = averageRating * ((count * coefficient) / 100);
                adjustedRating = averageRating + adjustment;
            } else {
                      adjustedRating = 0.0;
                      iterator.remove();
            }

            value.put(XLS_COLUMNS.get(5), round(averageRating, 2));
            value.put(XLS_COLUMNS.get(1), round(adjustedRating, 2));
        }
    }

    public static List<Object> getHeaders(Map<String, Map<String, Object>> directorsRating) {
        List<String> keysList = getKeysList(directorsRating);
        List<Object> headers = new ArrayList<Object>(directorsRating.get(keysList.get(0)).keySet());
        headers.add(0, XLS_COLUMNS.get(0));
        return headers;
    }

    public static List<String> getKeysList(Map<String, Map<String, Object>> directorsRating) {
        List<String> keysList = new ArrayList<String>(directorsRating.keySet());
        return keysList;
    }

    public static List<List<Object>> mapToNestedList(Map<String, Map<String, Object>> directorsRating) {
        Integer totalRows = directorsRating.size() + NUMBER_OF_HEADER_ROWS;
        List<String> keysList = getKeysList(directorsRating);
        List<List<Object>> directorsRatingList = new ArrayList<List<Object>>(totalRows);

        Integer count = 0;
        for (Map<String, Object> value : directorsRating.values()) {
            directorsRatingList.add(new ArrayList<Object>());
            List<Object> innerList = directorsRatingList.get(count);
            innerList.add(keysList.get(count));
            count += 1;

            for (Object innerValue : value.values()) {
                if (innerValue instanceof List) {
                    innerValue = String.join(", ", (List<String>) innerValue);
                }

                innerList.add(innerValue);
            }
        }
        return directorsRatingList;
    }

    public static void sortList(List<List<Object>> directorsRatingList) {
        Integer sortByColNumber = XLS_COLUMNS.indexOf(SORT_BY);
        Collections.sort(directorsRatingList, Collections.reverseOrder(Comparator.comparing(innerList ->
                         (Double) innerList.get(sortByColNumber))));
    };

    public static void writeToXlsx (List<List<Object>> directorsRatingList) throws IOException,
                                                                                   FileNotFoundException {
        Integer rows = directorsRatingList.size();
        Integer columns = directorsRatingList.get(0).size();

        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet(SHEET_NAME);

        for(int row = 0; row < rows; row++) {
            sheet.createRow(row);

            for(int column = 0; column < columns; column++) { 
                sheet.setColumnWidth(column, COLUMN_WIDTHS[column]);
                sheet.getRow(row).createCell(column);
                String cellValue = directorsRatingList.get(row).get(column).toString();
                sheet.getRow(row).getCell(column).setCellValue(cellValue);
            }   
        }

        try (OutputStream outputFile = new FileOutputStream(OUTPUT_FILENAME)) {
            workbook.write(outputFile);
        }
    }

    public static void openFile (String filename) throws IOException {
        File file = new File(filename);
        Desktop.getDesktop().open(file);
    }

    public static double round(double value, int places) {
        if (places < 0) throw new IllegalArgumentException();

        BigDecimal bigDecimalValue = BigDecimal.valueOf(value);
        bigDecimalValue = bigDecimalValue.setScale(places, RoundingMode.HALF_UP);
        return bigDecimalValue.doubleValue();
    }

    public static void main(String[] args) {
        Map<String, Map<String, Object>> directorsRating;

        try {
            directorsRating = parseCsvToMap();

        } catch (CsvValidationException e) { 
            System.err.println("This CSV file is invalid");
            return;

        } catch (UnsupportedEncodingException e) {
            System.err.println("The encoding is not valid");
            return;

        } catch (FileNotFoundException e) { 
            System.err.println("Please provide a filename for an existing file");
            return;

        } catch (IOException e) { 
            System.err.println("Error happened while reading CSV file");
            return;
        }

        calculateRatings(directorsRating);
        List<List<Object>> directorsRatingList = mapToNestedList(directorsRating);
        sortList(directorsRatingList);
        directorsRatingList.add(0, getHeaders(directorsRating));

        try {
            writeToXlsx(directorsRatingList);

        } catch (FileNotFoundException e) { 
            System.err.println("Please provide a filename for an existing file");
            return;

        } catch (IOException e) { 
            System.err.println("This workbook can't be written to the output stream");
            return;
        }

        try {
            openFile(OUTPUT_FILENAME);
        } catch (IOException e) {
            System.err.println("File has no associated application or the application fails to be launched");
            return;
        }
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ Could you please add some sample input and output? \$\endgroup\$ Dec 23, 2020 at 21:14
  • \$\begingroup\$ @JeremyHunt Added \$\endgroup\$ Dec 24, 2020 at 8:40

1 Answer 1

3
\$\begingroup\$
  1. Avoid using star imports. They tend to make your code fragile.

  2. Strictly observe the 80 character line limit. For a start, >we< cannot read your code on Code Review without adjusting the horizontal scroll bar in the viewer.

    (Now if it was only you reading your code, that wouldn't matter. But you asked us to read it, so it does matter.)

  3. It is better to use Arrays.asList(...) rather than Stream.of(...).collect(...) to populate a list that you don't intend to change.

  4. In fact, you could then wrap it using Collections.unmodifiableList to avoid any unintentional modifications.

  5. However, looking at the rest of the code, it may have need simpler if you had used arrays rather than lists for some or all of XLS_COLUMNS, KEYS and VALUES.

  6. Use of Map<String, Object> is a bit "code smell". The code would probably be better if that was a custom class:

    • The code would probably be simpler and easier to understand.
    • The code that uses the map could be made statically type-safe.
  7. There are simpler ways to round to 2 decimal places than using BigDecimal.

  8. The error messages in main are not particularly helpful. Depending on who the intended user is, you may be better off printing the exception message. Cases in point:

    • "This CSV file is invalid" ... gives no clues as to why it is invalid.
    • "The encoding is not valid" ... what encoding?
    • and so on.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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