From f198a1519f90ef8ee68199bd4edf93695a3476ee Mon Sep 17 00:00:00 2001 From: Jason Read Date: Fri, 18 Nov 2022 12:34:49 -0800 Subject: [PATCH 1/7] jread-13 jread completed day 13 challenge --- 13/jread/directors.py | 55 ++++++++++++++++++++++++++++++++++++++ 13/jread/test_directors.py | 33 +++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 13/jread/directors.py create mode 100644 13/jread/test_directors.py diff --git a/13/jread/directors.py b/13/jread/directors.py new file mode 100644 index 00000000..c41165c2 --- /dev/null +++ b/13/jread/directors.py @@ -0,0 +1,55 @@ +import csv +from collections import defaultdict, namedtuple, OrderedDict + +MOVIE_DATA = '../movie_metadata.csv' +NUM_TOP_DIRECTORS = 20 +MIN_MOVIES = 4 +MIN_YEAR = 1960 + +Director = namedtuple('Director', 'name avg_score movies') +Movie = namedtuple('Movie', 'title year score') + + +def get_movies_by_director(): + movies = defaultdict(list) + with open(MOVIE_DATA) as f: + for rec in csv.DictReader(f): + try: + name = rec['director_name'] + if int(rec['title_year']) >= MIN_YEAR: + movies[name].append(Movie(title=rec['movie_title'].replace('\xa0', ''), + year=int(rec['title_year']), score=float(rec['imdb_score']))) + except ValueError: + continue + + directors = {} + for name in movies: + if len(movies[name]) >= MIN_MOVIES: + directors[name] = (Director(name=name, avg_score=_calc_mean(movies[name]), movies=movies[name])) + + return directors + + +def _calc_mean(movies): + return round(sum(movie.score for movie in movies) / len(movies), 1) + + +def print_results(directors): + '''Print directors ordered by highest average rating. For each director + print his/her movies also ordered by highest rated movie. + See http://pybit.es/codechallenge13.html for example output''' + for i, director in zip(range(20), sorted(directors.items(), key=lambda x: float(x[1].avg_score), reverse=True)): + print() + print(f'{i+1:02d}. {director[0]:<52} {director[1].avg_score}') + print('-' * 60) + for movie in sorted(director[1].movies, key=lambda x: float(x.score), reverse=True): + print(f'{movie.year}] {movie.title:<50} {movie.score}') + + +def main(): + directors = get_movies_by_director() + print_results(directors) + + +if __name__ == '__main__': + main() diff --git a/13/jread/test_directors.py b/13/jread/test_directors.py new file mode 100644 index 00000000..95c0f96b --- /dev/null +++ b/13/jread/test_directors.py @@ -0,0 +1,33 @@ +from directors import get_movies_by_director, _calc_mean +from operator import itemgetter + + +def test(): + directors = get_movies_by_director() + + assert 'Sergio Leone' in directors + assert len(directors['Sergio Leone'].movies) == 4 + assert len(directors['Peter Jackson'].movies) == 12 + + movies_sergio = directors['Sergio Leone'].movies + movies_nolan = directors['Christopher Nolan'].movies + assert _calc_mean(movies_sergio) == 8.5 + assert _calc_mean(movies_nolan) == 8.4 + + assert 'Andrew Stanton' not in directors + + expected_directors = ['Sergio Leone', 'Christopher Nolan', 'Quentin Tarantino', + 'Hayao Miyazaki', 'Frank Darabont', 'Stanley Kubrick'] + expected_avg_scores = [8.5, 8.4, 8.2, 8.2, 8.0, 8.0] + expected_num_movies = [4, 8, 8, 4, 4, 7] + report = sorted(directors.items(), key=lambda x: float(x[1].avg_score), reverse=True) + for counter, (i, j, k) in enumerate(zip(expected_directors, expected_avg_scores, expected_num_movies)): + assert (report[counter][0], report[counter][1].avg_score) == (i, j) + assert len(report[counter][1].movies) == k + assert _calc_mean(report[counter][1].movies) == j + + return "tests pass" + + +if __name__ == '__main__': + print(test()) From c2c45a0f4d22a1f7b561481beab29aee2790c1e4 Mon Sep 17 00:00:00 2001 From: Jason Read Date: Sat, 19 Nov 2022 16:09:31 -0800 Subject: [PATCH 2/7] jread-13 Code cleanup --- 13/jread/directors.py | 5 +---- 13/jread/test_directors.py | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/13/jread/directors.py b/13/jread/directors.py index c41165c2..bc441e88 100644 --- a/13/jread/directors.py +++ b/13/jread/directors.py @@ -1,5 +1,5 @@ import csv -from collections import defaultdict, namedtuple, OrderedDict +from collections import defaultdict, namedtuple MOVIE_DATA = '../movie_metadata.csv' NUM_TOP_DIRECTORS = 20 @@ -35,9 +35,6 @@ def _calc_mean(movies): def print_results(directors): - '''Print directors ordered by highest average rating. For each director - print his/her movies also ordered by highest rated movie. - See http://pybit.es/codechallenge13.html for example output''' for i, director in zip(range(20), sorted(directors.items(), key=lambda x: float(x[1].avg_score), reverse=True)): print() print(f'{i+1:02d}. {director[0]:<52} {director[1].avg_score}') diff --git a/13/jread/test_directors.py b/13/jread/test_directors.py index 95c0f96b..695a7afe 100644 --- a/13/jread/test_directors.py +++ b/13/jread/test_directors.py @@ -1,6 +1,4 @@ from directors import get_movies_by_director, _calc_mean -from operator import itemgetter - def test(): directors = get_movies_by_director() From e903b9e51beed84b14e2e58b282786e1f94da352 Mon Sep 17 00:00:00 2001 From: Jason Read Date: Sat, 19 Nov 2022 17:21:06 -0800 Subject: [PATCH 3/7] jread-01 Completed exercise --- 01/jread/data.py | 6 +++++ 01/jread/test_wordvalue.py | 28 +++++++++++++++++++++ 01/jread/wordvalue.py | 50 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 01/jread/data.py create mode 100644 01/jread/test_wordvalue.py create mode 100644 01/jread/wordvalue.py diff --git a/01/jread/data.py b/01/jread/data.py new file mode 100644 index 00000000..39845833 --- /dev/null +++ b/01/jread/data.py @@ -0,0 +1,6 @@ +DICTIONARY = '../dictionary.txt' + +scrabble_scores = [(1, "E A O I N R T L S U"), (2, "D G"), (3, "B C M P"), + (4, "F H V W Y"), (5, "K"), (8, "J X"), (10, "Q Z")] +LETTER_SCORES = {letter: score for score, letters in scrabble_scores + for letter in letters.split()} diff --git a/01/jread/test_wordvalue.py b/01/jread/test_wordvalue.py new file mode 100644 index 00000000..0a2ae9bb --- /dev/null +++ b/01/jread/test_wordvalue.py @@ -0,0 +1,28 @@ +import unittest + +from data import DICTIONARY, LETTER_SCORES +from wordvalue import load_words, calc_word_value, max_word_value + +TEST_WORDS = ('bob', 'julian', 'pybites', 'quit', 'barbeque') + +class TestWordValue(unittest.TestCase): + + def test_load_words(self): + words = load_words() + self.assertEqual(len(words), 235886) + self.assertEqual(words[0], 'A') + self.assertEqual(words[-1], 'Zyzzogeton') + self.assertNotIn(' ', ''.join(words)) + + def test_calc_word_value(self): + self.assertEqual(calc_word_value('bob'), 7) + self.assertEqual(calc_word_value('JuliaN'), 13) + self.assertEqual(calc_word_value('PyBites'), 14) + self.assertEqual(calc_word_value('benzalphenylhydrazone'), 56) + + def test_max_word_value(self): + self.assertEqual(max_word_value(TEST_WORDS), 'barbeque') + self.assertEqual(max_word_value(), 'benzalphenylhydrazone') + +if __name__ == "__main__": + unittest.main() diff --git a/01/jread/wordvalue.py b/01/jread/wordvalue.py new file mode 100644 index 00000000..a7027eb3 --- /dev/null +++ b/01/jread/wordvalue.py @@ -0,0 +1,50 @@ +import re + +from data import DICTIONARY, LETTER_SCORES + +CHARS_REGEX = re.compile('[a-zA-Z]+') + + +def load_words() -> list: + """ + Loads words from the file designated by the DICTIONARY constant into a list + (trailing newline characters are stripped) + @return: the words in DICTIONARY as a list + """ + words: list + with open(DICTIONARY) as f: + words = [line.rstrip('\n') for line in f] + return words + + +def calc_word_value(word: str) -> int: + """ + Calculates the scabble value of the word specified + @param word: the word value to calculate the scabble value for + @return: the scrabble value (an integer) + """ + value: int = 0 + for c in ''.join(CHARS_REGEX.findall(word)): + value += LETTER_SCORES[c.upper()] + return value + + +def max_word_value(dictionary: list = None) -> str: + """ + Determine the word in dictionary which provides the highest scrabble score + @param dictionary: an optional list of alternate words to check. If not specified, load_words() will be used + @return: the word in dictionary providing the highest scrabble score + """ + dictionary = load_words() if dictionary is None else dictionary + word: str = '' + value: int = 0 + for check_word in dictionary: + check_word_value = calc_word_value(check_word) + if check_word_value > value: + word = check_word + value = check_word_value + return word + + +if __name__ == "__main__": + print(max_word_value()) From cc01d72fc435fb89e489e7fbf240e5b58686c789 Mon Sep 17 00:00:00 2001 From: Jason Read Date: Sat, 19 Nov 2022 17:25:57 -0800 Subject: [PATCH 4/7] jread-13 Completed exercise --- 13/jread/directors.py | 1 + 1 file changed, 1 insertion(+) diff --git a/13/jread/directors.py b/13/jread/directors.py index bc441e88..1966a6c1 100644 --- a/13/jread/directors.py +++ b/13/jread/directors.py @@ -50,3 +50,4 @@ def main(): if __name__ == '__main__': main() + From be45ca08945622538a62b9c91723117695d43ecf Mon Sep 17 00:00:00 2001 From: Jason Read Date: Sat, 19 Nov 2022 17:27:18 -0800 Subject: [PATCH 5/7] jread-01 Completed exercise --- 01/jread/wordvalue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/01/jread/wordvalue.py b/01/jread/wordvalue.py index a7027eb3..832f7909 100644 --- a/01/jread/wordvalue.py +++ b/01/jread/wordvalue.py @@ -48,3 +48,4 @@ def max_word_value(dictionary: list = None) -> str: if __name__ == "__main__": print(max_word_value()) + From 58daa670df91dfb931d286770ca09a311512414c Mon Sep 17 00:00:00 2001 From: Jason Read Date: Sat, 19 Nov 2022 17:31:06 -0800 Subject: [PATCH 6/7] jread-01 Completed exercise --- 01/jread/test_wordvalue.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/01/jread/test_wordvalue.py b/01/jread/test_wordvalue.py index 0a2ae9bb..70a0e028 100644 --- a/01/jread/test_wordvalue.py +++ b/01/jread/test_wordvalue.py @@ -25,4 +25,5 @@ def test_max_word_value(self): self.assertEqual(max_word_value(), 'benzalphenylhydrazone') if __name__ == "__main__": - unittest.main() + unittest.main() + From 8c9d3c41face09e2503cebcb465412214de62f7e Mon Sep 17 00:00:00 2001 From: Jason Read Date: Sat, 26 Nov 2022 16:44:23 -0800 Subject: [PATCH 7/7] jread-39 completed challenge 39 using app.py flask example code --- 39/jread/app.py | 101 +++++++++++++++++++++++++++++++++++++++++++ 39/jread/test_app.py | 53 +++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 39/jread/app.py create mode 100644 39/jread/test_app.py diff --git a/39/jread/app.py b/39/jread/app.py new file mode 100644 index 00000000..2d58c6c9 --- /dev/null +++ b/39/jread/app.py @@ -0,0 +1,101 @@ +from flask import Flask, jsonify, abort, make_response, request + +NOT_FOUND = 'Not found' +BAD_REQUEST = 'Bad request' + +app = Flask(__name__) + +items = [ + { + 'id': 1, + 'name': 'laptop', + 'value': 1000 + }, + { + 'id': 2, + 'name': 'chair', + 'value': 300, + }, + { + 'id': 3, + 'name': 'book', + 'value': 20, + }, +] + + +def _get_item(id): + return [item for item in items if item['id'] == id] + + +def _record_exists(name): + return [item for item in items if item["name"] == name] + + +@app.errorhandler(404) +def not_found(error): + return make_response(jsonify({'error': NOT_FOUND}), 404) + + +@app.errorhandler(400) +def bad_request(error): + return make_response(jsonify({'error': BAD_REQUEST}), 400) + + +@app.route('/api/v1.0/items', methods=['GET']) +def get_items(): + return jsonify({'items': items}) + + +@app.route('/api/v1.0/items/', methods=['GET']) +def get_item(id): + item = _get_item(id) + if not item: + abort(404) + return jsonify({'items': item}) + + +@app.route('/api/v1.0/items', methods=['POST']) +def create_item(): + if not request.json or 'name' not in request.json or 'value' not in request.json: + abort(400) + item_id = items[-1].get("id") + 1 + name = request.json.get('name') + if _record_exists(name): + abort(400) + value = request.json.get('value') + if type(value) is not int: + abort(400) + item = {"id": item_id, "name": name, + "value": value} + items.append(item) + return jsonify({'item': item}), 201 + + +@app.route('/api/v1.0/items/', methods=['PUT']) +def update_item(id): + item = _get_item(id) + if len(item) == 0: + abort(404) + if not request.json: + abort(400) + name = request.json.get('name', item[0]['name']) + value = request.json.get('value', item[0]['value']) + if type(value) is not int: + abort(400) + item[0]['name'] = name + item[0]['value'] = value + return jsonify({'item': item[0]}), 200 + + +@app.route('/api/v1.0/items/', methods=['DELETE']) +def delete_item(id): + item = _get_item(id) + if len(item) == 0: + abort(404) + items.remove(item[0]) + return jsonify({}), 204 + + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/39/jread/test_app.py b/39/jread/test_app.py new file mode 100644 index 00000000..54329191 --- /dev/null +++ b/39/jread/test_app.py @@ -0,0 +1,53 @@ +import app +import json +import pytest + +CLIENT = app.app.test_client() + + +@pytest.mark.parametrize('item_id', [1, 2, 3]) +def test_get_item(item_id: int): + response = CLIENT.get(f'/api/v1.0/items/{item_id}') + assert json.loads(response.data)['items'][0] == app.items[item_id - 1] + + +def test_get_item_not_found(): + response = CLIENT.get('/api/v1.0/items/100') + assert response.status_code == 404 + + +def test_get_items(): + response = CLIENT.get('/api/v1.0/items') + assert json.loads(response.data)['items'] == app.items + + +@pytest.mark.parametrize('name, value', [ + ('monitor', 500), + ('desk', 1000), + ('keyboard', 100) +]) +def test_create_item(name: str, value: int): + item = {'name': name, 'value': value} + response = CLIENT.post('/api/v1.0/items', data=json.dumps(item), content_type='application/json') + assert response.status_code == 201 + assert json.loads(response.data)['item'] == app.items[-1] + + +@pytest.mark.parametrize('value, item_id', [ + (500, 1), + (1000, 2) +]) +def test_update_item(value: int, item_id: int): + item = json.loads(CLIENT.get(f'/api/v1.0/items/{item_id}').data)['items'][0] + item['value'] = value + response = CLIENT.put(f'/api/v1.0/items/{item_id}', data=json.dumps(item), content_type='application/json') + assert json.loads(response.data)['item'] == item + + +@pytest.mark.parametrize('item_id, status_code', [ + (1, 204), + (1, 404) +]) +def test_delete_item(item_id: int, status_code: int): + response = CLIENT.delete(f'/api/v1.0/items/{item_id}') + assert response.status_code == status_code \ No newline at end of file