├── .gitignore ├── .ipynb_checkpoints ├── Untitled-checkpoint.ipynb └── Untitled1-checkpoint.ipynb ├── README.md ├── ch01-numbers ├── .ipynb_checkpoints │ ├── Chapter 1 — Numbers-checkpoint.ipynb │ └── Chapter 2 — Strings-checkpoint.ipynb ├── Chapter 1 — Numbers.ipynb ├── e01_guessing_game.py ├── e01b1_3_chances.py ├── e01b2_guess_base.py ├── e01b3_guess_word.py ├── e02_summing_numbers.py ├── e02b1_mysum_start.py ├── e02b2_mean.py ├── e02b3_word_summary.py ├── e02b4_sum_intable.py ├── e03_run_timing.py ├── e03b1_before_after.py ├── e03b2_decimal_add.py ├── e04_hex_output.py ├── e04b1_ord_chr.py ├── e04b2_name_triangle.py ├── test_e01_guessing_game.py ├── test_e02_summing_numbers.py ├── test_e03_run_timing.py ├── test_e04_hex_output.py └── words.txt ├── ch02-strings ├── .gitignore ├── Chapter 2 — Strings.ipynb ├── e05_pig_latin.py ├── e05b1_capitalized.py ├── e05b2_punctuation.py ├── e05b3_different_vowels.py ├── e06_pig_latin_sentence.py ├── e06b1_word_per_line.py ├── e06b2_transpose_strings.py ├── e06b3_404_ips.py ├── e07_ubbi_dubbi.py ├── e07b1_capital.py ├── e07b2_remove_authors.py ├── e07b3_url_encode.py ├── e08_sort_string.py ├── e08b1_sort_words.py ├── e08b2_last_word_in_file.py ├── e08b3_longest_word_in_file.py ├── test_e05_pig_latin.py ├── test_e06_pig_latin_sentence.py ├── test_e07_ubbi_dubbi.py └── test_e08_sort_string.py ├── ch03-lists-tuples ├── .ipynb_checkpoints │ └── Chatper 3 — Lists and tuples-checkpoint.ipynb ├── Chatper 3 — Lists and tuples.ipynb ├── e09_firstlast.py ├── e09b1_square_all.py ├── e09b2_largest_element.py ├── e09b3_largest_word.py ├── e09b4_even_odd_sums.py ├── e09b5_plus_minus.py ├── e09b6_myzip.py ├── e10_sum_anything.py ├── e10b1_mysum_bigger_than.py ├── e10b2_sum_numeric.py ├── e10b3_combine_dict.py ├── e11_alphabetize_names.py ├── e11b1_sort_absolute.py ├── e11b2_sort_by_vowel_count.py ├── e11b3_sort_by_sum.py ├── e12_most_repeating_letters.py ├── e12b1_most_repeated_vowels.py ├── e12b2_popular_shells.py ├── e12b3_shells_and_users.py ├── e13_tuple_records.py ├── e13b1_namedtuple_records.py ├── e13b2_sorted_movies.py ├── e13b3_sorted_movies_multifield.py ├── test_e09_firstlast.py ├── test_e10_sum_anything.py ├── test_e11_alphabetize_names.py ├── test_e12_most_repeating_letters.py └── test_e13_tuple_records.py ├── ch04-dicts ├── .ipynb_checkpoints │ ├── Chapter 4 — Dicts and sets-checkpoint.ipynb │ └── Chapter 5 — Files-checkpoint.ipynb ├── Chapter 4 — Dicts and sets.ipynb ├── e14_restaurant.py ├── e14b1_simple_login.py ├── e14b2_temps.py ├── e14b3_days_old.py ├── e15_rain.py ├── e15b1_average.py ├── e15b2_error_ips.py ├── e15b3_word_lengths.py ├── e16_dictdiff.py ├── e16b1_multiupdate.py ├── e16b2_dict_from_list.py ├── e16b3_partition_dict.py ├── e17_different_numbers.py ├── e17b1_different_ips.py ├── e17b2_different_responses.py ├── e17b3_different_extensions.py ├── test_e14_restaurant.py ├── test_e15_rain.py ├── test_e16_dictdiff.py └── test_e17_different_numbers.py ├── ch05-files ├── .ipynb_checkpoints │ ├── Chapter 5 — Files-checkpoint.ipynb │ └── Untitled-checkpoint.ipynb ├── Chapter 5 — Files.ipynb ├── e18_final_line.py ├── e18b1_sum_ints.py ├── e18b2_sum_mult_columns.py ├── e18b3_count_vowels.py ├── e19_passwd_to_dict.py ├── e19b1_shells_users.py ├── e19b2_factors.py ├── e19b3_user_info.py ├── e20_wc.py ├── e20b1_count_certain_words.py ├── e20b2_file_sizes.py ├── e20b3_most_common_letters.py ├── e21_longest_word.py ├── e21b1_md5_files.py ├── e21b2_mod_time.py ├── e21b3_response_counts.py ├── e22_passwd_to_csv.py ├── e22b1_passwd_to_csv_selected.py ├── e22b2_dict_to_csv.py ├── e22b3_random.csv.py ├── e23_test_scores.py ├── e23b1_json_passwd.py ├── e23b2_json_passwd_dict.py ├── e23b3_file_info.py ├── e24_reverse_lines.py ├── e24b1_encrypt.py ├── e24b2_vowels_and_consonants.py ├── e24b3_shell_users.py ├── info.txt ├── json-files.zip ├── myoutput.txt ├── myshells.txt ├── output.csv ├── passwd-consonants ├── passwd-vowels ├── passwd.csv ├── scores │ ├── 9a.json │ ├── 9b.json │ ├── json-files.zip │ └── json-files │ │ ├── 9a.json │ │ └── 9b.json ├── test_e18_final_line.py ├── test_e19_passwd_to_dict.py ├── test_e20_wc.py ├── test_e21_longest_word.py ├── test_e22_passwd_to_csv.py ├── test_e23_test_scores.py ├── test_e24_reverse_lines.py └── test_file.txt ├── ch06-functions ├── .ipynb_checkpoints │ ├── Chapter 6 — Functions-checkpoint.ipynb │ └── Untitled-checkpoint.ipynb ├── Chapter 6 — Functions.ipynb ├── e25_xml.py ├── e25b1_copyfile.py ├── e25b2_factorial.py ├── e25b3_anyjoin.py ├── e26_calc.py ├── e26b1_calc_args.py ├── e26b2_apply_to_each.py ├── e26b3_transform_lines.py ├── e27_makepw.py ├── e27b1_password_checker.py ├── e27b2_getitem.py ├── e27b3_doboth.py ├── test_e25_xml.py ├── test_e26_calc.py └── test_e27_makepw.py ├── ch07-comprehensions ├── .ipynb_checkpoints │ └── Chapter 7 — Comprehensions-checkpoint.ipynb ├── Chapter 7 — Comprehensions.ipynb ├── cities.json ├── e28_join_numbers.py ├── e28b1_under_10.py ├── e28b2_sum_hexes.py ├── e28b3_reverse_words.py ├── e29_sum_numbers.py ├── e29b1_1v20c_lines.py ├── e29b2_increment_area_code.py ├── e29b3_age_in_months.py ├── e30_flatten_list.py ├── e30b1_flatten_odd_ints.py ├── e30b2_grandchildren_names.py ├── e30b3_sorted_grandchildren.py ├── e31_pig_latin_file.py ├── e31b1_funcfile.py ├── e31b2_dicts_to_tuples.py ├── e31b3_most_popular_hobbies.py ├── e32_flipped_dict.py ├── e32b1_word_vowels.py ├── e32b2_file_info.py ├── e32b3_read_config.py ├── e33_transform_values.py ├── e33b1_transform_values2.py ├── e33b2_passwd_to_dict.py ├── e33b3_file_info.py ├── e34_supervocalic.py ├── e34b1_different_shells.py ├── e34b2_word_lengths.py ├── e34b3_letters_in_names.py ├── e35a_gematria_1.py ├── e35ab1_read_config.py ├── e35ab2_read_config_int.py ├── e35ab3_cities.py ├── e35b_gematria_2.py ├── e35bb1_temp.py ├── e35bb2_books.py ├── e35bb3_currency_conversion.py ├── test_e28_join_numbers.py ├── test_e29_sum_numbers.py ├── test_e30_flatten_list.py ├── test_e31_pig_latin_file.py ├── test_e32_flipped_dict.py ├── test_e33_transform_values.py ├── test_e35a_gematria_1.py ├── test_e35b_gematria_2.py └── words.txt ├── ch08-modules ├── e36_freedonia.py ├── e36b1_tax_brackets.py ├── e36b2_analyze_string.py ├── e36b3_fromkeys_func.py ├── e37_menu.py ├── e37b1_selftest.py ├── e37b2_menu │ ├── README.rst │ ├── e37b2_menu │ │ ├── __init__.py │ │ └── e37b2_menu.py │ ├── pyproject.toml │ └── tests │ │ ├── __init__.py │ │ └── test_e37b2_menu.py ├── e37b3_stuff.py ├── screencasts │ ├── e36_freedonia.py │ ├── menu.py │ ├── use_freedonia.py │ └── use_menu.py ├── test_e36_freedonia.py └── test_e37_menu.py ├── ch09-objects ├── .ipynb_checkpoints │ └── Chapter 8 — Objects-checkpoint.ipynb ├── Chapter 8 — Objects.ipynb ├── e38_scoop.py ├── e38b1_beverage.py ├── e38b2_beverage_default_temp.py ├── e38b3_logfile.py ├── e39_bowl.py ├── e39b1_book_and_shelf.py ├── e39b2_has_book.py ├── e39b3_book_width.py ├── e40_limited_size_bowl.py ├── e40b1_population.py ├── e40b2_population_del.py ├── e40b3_transaction.py ├── e41_bigbowl.py ├── e41b1_envelope.py ├── e41b2_phone.py ├── e41b3_bread.py ├── e42_flexible_dict.py ├── e42b1_string_key_dict.py ├── e42b2_recent_dict.py ├── e42b3_flatlist.py ├── e43_animals.py ├── e43b1_legged_animals.py ├── e43b2_class_legs.py ├── e43b3_animal_noises.py ├── e44_cages.py ├── e44b1_big_cage.py ├── e44b2_animal_space.py ├── e44b3_animal_safety.py ├── e45_zoo.py ├── e45b1_any_colors.py ├── e45b2_transfer_zoo.py ├── e45b3_color_and_legs.py ├── test_e38_scoop.py ├── test_e39_bowl.py ├── test_e40_limited_size_bowl.py ├── test_e41_bigbowl.py ├── test_e42_flexible_dict.py ├── test_e43_animals.py ├── test_e44_cages.py └── test_e45_zoo.py ├── ch10-iterators ├── .ipynb_checkpoints │ └── Chapter 10 — Iterators and generators-checkpoint.ipynb ├── Chapter 10 — Iterators and generators.ipynb ├── e46_myenumerate.py ├── e46b1_enumerate_helper.py ├── e46b2_enumerate_with_default.py ├── e46b3_enumerate_generator.py ├── e47_circle.py ├── e47b1_circle_inherit.py ├── e47b2_circle_generator.py ├── e47b3_myrange.py ├── e48_all_lines.py ├── e48b1_all_lines_tuple.py ├── e48b2_all_lines_alt.py ├── e48b3_all_lines_matching.py ├── e49_elapsed_since.py ├── e49b1_elapsed_since_wait.py ├── e49b2_file_usage_timing.py ├── e49b3_yield_filter.py ├── e50_mychain.py ├── e50b1_zip.py ├── e50b2_all_lines_mychain.py ├── e50b3_myrange_generator.py ├── test_e46_myenumerate.py ├── test_e47_circle.py ├── test_e48_all_lines.py ├── test_e49_elapsed_since.py └── test_e50_mychain.py ├── files ├── 1342-0.txt ├── 2701-0.txt ├── 43-0.txt ├── 46-0.txt ├── 61105-0.txt ├── 84-0.txt ├── books.zip ├── linux-etc-passwd.txt ├── mini-access-log.txt ├── nums.txt ├── output.txt ├── pg25525.txt ├── pg28860.txt ├── pg345.txt ├── pg514.txt ├── reverse_passwd.txt ├── shoe-data.txt └── wcfile.txt ├── output.csv └── output.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | htmlcov/* 3 | htmlcov 4 | .ipynb_checkpoints 5 | Untitled*ipynb 6 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/Untitled-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 2 6 | } 7 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/Untitled1-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 4 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code files from "Python Workout" 2 | 3 | My book, "Python Workout" (https://PythonWorkout.com/) contains 50 exercises that help to improve your Python fluency. This repository contains the code and solutions to those exercises, including all of the "beyond the exercise" additional, bonus exercises. 4 | 5 | The main exercises have "pytest" tests, while the "beyond" exercises don't. 6 | 7 | While you're welcome to look at this code, you should *NOT* look at it before you work on a solution by yourself! You'll learn the most by actually putting in the work, and trying to solve the problems. Looking at the answer isn't nearly as useful to your learning. 8 | 9 | Don't forget that my free, weekly "Better developers" newsletter (currently read by about 18,000 people) contains a new Python-related article each week. Sign up at https://BetterDevelopersWeekly.com/ . 10 | 11 | Enjoy! 12 | 13 | _Reuven_ 14 | -------------------------------------------------------------------------------- /ch01-numbers/.ipynb_checkpoints/Chapter 2 — Strings-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 2 6 | } 7 | -------------------------------------------------------------------------------- /ch01-numbers/e01_guessing_game.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 1: Guessing game""" 3 | import random 4 | 5 | 6 | def guessing_game(): 7 | """Generate a random integer from 1 to 100. 8 | 9 | Ask the user repeatedly to guess the number. 10 | Until they guess correctly, tell them to guess higher or lower. 11 | """ 12 | answer = random.randint(0, 100) 13 | 14 | while True: 15 | user_guess = int(input('What is your guess? ')) 16 | 17 | if user_guess == answer: 18 | print(f'Right! The answer is {user_guess}') 19 | break 20 | 21 | if user_guess < answer: 22 | print(f'Your guess of {user_guess} is too low!') 23 | 24 | else: 25 | print(f'Your guess of {user_guess} is too high!') 26 | -------------------------------------------------------------------------------- /ch01-numbers/e01b1_3_chances.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 1, beyond 1: Guessing game, only 3 chances""" 3 | import random 4 | 5 | 6 | def guessing_game(): 7 | """Generate a random integer from 1 to 100. 8 | 9 | Ask the user repeatedly to guess the number. 10 | Until they guess correctly, tell them to guess higher or lower. 11 | If they take more than three times to guess, the program 12 | tells them that they're out of guesses. 13 | """ 14 | answer = random.randint(0, 100) 15 | remaining_guesses = 2 16 | 17 | while remaining_guesses >= 0: 18 | remaining_guesses -= 1 19 | user_guess = int(input('What is your guess? ')) 20 | 21 | if user_guess == answer: 22 | print(f'Right! The answer is {user_guess}') 23 | break 24 | 25 | if user_guess < answer: 26 | print(f'Your guess of {user_guess} is too low!') 27 | 28 | else: 29 | print(f'Your guess of {user_guess} is too high!') 30 | 31 | else: 32 | print('Your three chances are up!') 33 | -------------------------------------------------------------------------------- /ch01-numbers/e01b2_guess_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 1, beyond 2: Guessing game, number bases""" 3 | import random 4 | 5 | 6 | def guessing_game(): 7 | """Generate a random integer from 1 to 100. 8 | 9 | Ask the user repeatedly to guess the number. 10 | Until they guess correctly, tell them to guess higher or lower. 11 | 12 | The program chooses a random number base for the user's input, 13 | as well as a random number. 14 | 15 | NOTE: This game might be considered torture under the Geneva conventions. 16 | """ 17 | answer = random.randint(0, 100) 18 | required_base = random.choice([2, 8, 10, 16]) # binary/octal/decimal/hex 19 | 20 | while True: 21 | user_guess = int(input('What is your guess? '), required_base) 22 | 23 | if user_guess == answer: 24 | print(f'Right! The answer is {user_guess}') 25 | break 26 | 27 | if user_guess < answer: 28 | print(f'Your guess of {user_guess} is too low!') 29 | 30 | else: 31 | print(f'Your guess of {user_guess} is too high!') 32 | -------------------------------------------------------------------------------- /ch01-numbers/e01b3_guess_word.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 1, beyond 3: Guessing game, words""" 3 | import random 4 | 5 | WORDS = [one_word.strip() 6 | for one_word in open('words.txt')] 7 | 8 | 9 | def guessing_game(): 10 | """Choose a random word from the dictionary. 11 | 12 | Ask the user repeatedly to guess a word. 13 | Until they guess correctly, tell them to guess one that's 14 | earlier or later in the dictionary. 15 | """ 16 | answer = random.choice(WORDS) 17 | 18 | while True: 19 | user_guess = int(input('What is your guess? ')) 20 | 21 | if user_guess == answer: 22 | print(f'Right! The answer is {user_guess}') 23 | break 24 | 25 | if user_guess < answer: 26 | print(f'Your guess of {user_guess} is too low!') 27 | 28 | else: 29 | print(f'Your guess of {user_guess} is too high!') 30 | -------------------------------------------------------------------------------- /ch01-numbers/e02_summing_numbers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 2: mysum""" 3 | 4 | 5 | def mysum(*numbers): 6 | """Accepts any number of numeric arguments as inputs. 7 | Returns the sum of those numbers. 8 | If invoked without any arguments, returns 0. 9 | """ 10 | output = 0 11 | for number in numbers: 12 | output += number 13 | return output 14 | -------------------------------------------------------------------------------- /ch01-numbers/e02b1_mysum_start.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 2, beyond 1: mysum with start""" 3 | 4 | 5 | def mysum(numbers, start=0): 6 | """Accepts any number of numeric arguments as inputs. 7 | Returns the sum of those numbers, plus the value of "start", 8 | which defaults to 0. 9 | """ 10 | output = start 11 | for number in numbers: 12 | output += number 13 | return output 14 | -------------------------------------------------------------------------------- /ch01-numbers/e02b2_mean.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 2, beyond 2: mean""" 3 | 4 | 5 | def mean(numbers): 6 | """Accepts a non-empty list of numbers. 7 | 8 | Returns the mean of those numbers. 9 | """ 10 | return sum(numbers) / len(numbers) 11 | -------------------------------------------------------------------------------- /ch01-numbers/e02b3_word_summary.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 2, beyond 3: words summary""" 3 | 4 | 5 | def summarize(words): 6 | """Accepts a list of strings. 7 | 8 | Returns a 3-element tuple containing three integers: (a) length 9 | of the shortest word, (b) length of the longest word, and (c) 10 | average word length. 11 | """ 12 | word_lengths = [len(one_word) 13 | for one_word in words] 14 | 15 | return min(word_lengths), max(word_lengths), sum(word_lengths)/len(word_lengths) 16 | -------------------------------------------------------------------------------- /ch01-numbers/e02b4_sum_intable.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 2, beyond 4: sum intable""" 3 | 4 | 5 | def is_intable(one_item): 6 | try: 7 | int(one_item) 8 | return True 9 | except ValueError: 10 | return False 11 | 12 | 13 | def sum_intable(items): 14 | """Accepts a list of Python objects. 15 | 16 | Sums those objects that are integers or can be 17 | turned into integers. 18 | """ 19 | 20 | return sum(one_item 21 | for one_item in items 22 | if is_intable(one_item)) 23 | -------------------------------------------------------------------------------- /ch01-numbers/e03_run_timing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 3: run_timing""" 3 | 4 | 5 | def run_timing(): 6 | """Asks the user repeatedly for numeric input. 7 | Prints the average time and number of runs. 8 | """ 9 | 10 | number_of_runs = 0 11 | total_time = 0 12 | 13 | while True: 14 | one_run = input('Enter 10 km run time: ') 15 | 16 | if not one_run: 17 | break 18 | 19 | number_of_runs += 1 20 | total_time += float(one_run) 21 | 22 | average_time = total_time / number_of_runs 23 | 24 | print(f'Average of {average_time}, over {number_of_runs} runs') 25 | -------------------------------------------------------------------------------- /ch01-numbers/e03b1_before_after.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 3, beyond 1: before_after_dot""" 3 | 4 | 5 | def before_after_dot(f, before, after): 6 | """Accepts a float, and two integers. 7 | 8 | Returns a float containing before digits preceding the dcimal point, 9 | and after digits following the decimal point. 10 | """ 11 | s = str(f) 12 | i = s.index('.') 13 | return s[i-before:i+after+1] 14 | -------------------------------------------------------------------------------- /ch01-numbers/e03b2_decimal_add.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 3, beyond 2: decimal_add""" 3 | 4 | from decimal import Decimal 5 | 6 | 7 | def decimal_add(first, second): 8 | """Accepts two strings, turns them into decimals, and returns a float 9 | representing the sum of these two. 10 | """ 11 | return float(Decimal(first) + Decimal(second)) 12 | -------------------------------------------------------------------------------- /ch01-numbers/e04_hex_output.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 4: hex_output""" 3 | 4 | 5 | def hex_output(): 6 | """Ask the user to enter a valid hexadecimal 7 | number, and print the decimal equivalent. 8 | """ 9 | 10 | decnum = 0 11 | hexnum = input('Enter a hex number to convert: ') 12 | for power, digit in enumerate(reversed(hexnum)): 13 | decnum += int(digit, 16) * (16 ** power) 14 | print(decnum) 15 | -------------------------------------------------------------------------------- /ch01-numbers/e04b1_ord_chr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 4, beyond 1: ord_hex""" 3 | 4 | 5 | def ord_hex_output(): 6 | """Get a hex number to convert. Use ord to turn it into an integer, 7 | and print the decimal equivalent. 8 | """ 9 | 10 | decnum = 0 11 | hexnum = input('Enter a hex number to convert: ') 12 | for power, digit in enumerate(reversed(hexnum)): 13 | if 48 <= ord(digit) <= 57: 14 | dec_digit = ord(digit) - 48 15 | elif 97 <= ord(digit) <= 102: 16 | dec_digit = ord(digit) - 87 17 | 18 | decnum += dec_digit * (16 ** power) 19 | print(decnum) 20 | -------------------------------------------------------------------------------- /ch01-numbers/e04b2_name_triangle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 1, exercise 4, beyond 2: name_triangle""" 3 | 4 | 5 | def name_triangle(): 6 | """Get the user's name. Print a name triangle, starting 7 | with the first letter, then the first two letters, etc. 8 | """ 9 | name = input("Enter your name: ") 10 | 11 | for i in range(len(name)): 12 | print(name[:i+1]) 13 | -------------------------------------------------------------------------------- /ch01-numbers/test_e01_guessing_game.py: -------------------------------------------------------------------------------- 1 | from e01_guessing_game import guessing_game 2 | from io import StringIO 3 | import random 4 | 5 | 6 | def test_correct(monkeypatch, capsys): 7 | random.seed(0) # first randint will be 100 8 | monkeypatch.setattr('sys.stdin', StringIO('49\n')) 9 | guessing_game() 10 | captured_out, captured_err = capsys.readouterr() 11 | 12 | assert captured_out.endswith('Right! The answer is 49\n') 13 | 14 | 15 | def test_too_low_once(monkeypatch, capsys): 16 | random.seed(0) # first randint will be 100 17 | monkeypatch.setattr('sys.stdin', StringIO('1\n49\n')) 18 | guessing_game() 19 | captured_out, captured_err = capsys.readouterr() 20 | 21 | assert 'Your guess of 1 is too low!' in captured_out 22 | assert captured_out.endswith('Right! The answer is 49\n') 23 | 24 | 25 | def test_too_high_once(monkeypatch, capsys): 26 | random.seed(0) # first randint will be 100 27 | monkeypatch.setattr('sys.stdin', StringIO('100\n49\n')) 28 | guessing_game() 29 | captured_out, captured_err = capsys.readouterr() 30 | 31 | assert 'Your guess of 100 is too high!' in captured_out 32 | assert captured_out.endswith('Right! The answer is 49\n') 33 | 34 | 35 | def test_too_low_twice(monkeypatch, capsys): 36 | random.seed(0) # first randint will be 100 37 | monkeypatch.setattr('sys.stdin', StringIO('1\n2\n49\n')) 38 | guessing_game() 39 | captured_out, captured_err = capsys.readouterr() 40 | 41 | assert 'Your guess of 1 is too low!' in captured_out 42 | assert 'Your guess of 2 is too low!' in captured_out 43 | assert captured_out.endswith('Right! The answer is 49\n') 44 | 45 | 46 | def test_too_high_twice(monkeypatch, capsys): 47 | random.seed(0) # first randint will be 100 48 | monkeypatch.setattr('sys.stdin', StringIO('100\n80\n49\n')) 49 | guessing_game() 50 | captured_out, captured_err = capsys.readouterr() 51 | 52 | assert 'Your guess of 100 is too high!' in captured_out 53 | assert 'Your guess of 80 is too high!' in captured_out 54 | assert captured_out.endswith('Right! The answer is 49\n') 55 | -------------------------------------------------------------------------------- /ch01-numbers/test_e02_summing_numbers.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from e02_summing_numbers import mysum 3 | 4 | 5 | @pytest.mark.parametrize('inputs, expected_sum', [ 6 | ((), 0), 7 | ((10,), 10), 8 | ((10, 20, 30), 60), 9 | ((10.5, 20, 30), 60.5) 10 | ]) 11 | def test_mysum(inputs, expected_sum): 12 | assert mysum(*inputs) == expected_sum 13 | -------------------------------------------------------------------------------- /ch01-numbers/test_e03_run_timing.py: -------------------------------------------------------------------------------- 1 | from e03_run_timing import run_timing 2 | from io import StringIO 3 | import pytest 4 | 5 | 6 | def test_no_input_generates_exception(monkeypatch, capsys): 7 | monkeypatch.setattr('sys.stdin', StringIO('\n')) 8 | 9 | with pytest.raises(ZeroDivisionError): 10 | run_timing() 11 | 12 | 13 | def test_bad_input_generates_exception(monkeypatch, capsys): 14 | monkeypatch.setattr('sys.stdin', StringIO('abc\n\n')) 15 | 16 | with pytest.raises(ValueError): 17 | run_timing() 18 | 19 | 20 | def test_one_run(monkeypatch, capsys): 21 | monkeypatch.setattr('sys.stdin', StringIO('1\n\n')) 22 | 23 | run_timing() 24 | captured_out, captured_err = capsys.readouterr() 25 | 26 | assert captured_out.endswith("Average of 1.0, over 1 runs\n") 27 | 28 | 29 | def test_10_runs(monkeypatch, capsys): 30 | monkeypatch.setattr('sys.stdin', StringIO( 31 | '1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n\n')) 32 | 33 | run_timing() 34 | captured_out, captured_err = capsys.readouterr() 35 | 36 | assert captured_out.endswith("Average of 5.5, over 10 runs\n") 37 | -------------------------------------------------------------------------------- /ch01-numbers/test_e04_hex_output.py: -------------------------------------------------------------------------------- 1 | from e04_hex_output import hex_output 2 | from io import StringIO 3 | import pytest 4 | 5 | 6 | def test_no_input(monkeypatch, capsys): 7 | monkeypatch.setattr('sys.stdin', StringIO('\n')) 8 | hex_output() 9 | captured_out, captured_err = capsys.readouterr() 10 | assert captured_out.endswith('0\n') 11 | 12 | 13 | def test_bad_input(monkeypatch, capsys): 14 | monkeypatch.setattr('sys.stdin', StringIO('q\n')) 15 | with pytest.raises(ValueError): 16 | hex_output() 17 | 18 | 19 | @pytest.mark.parametrize('user_input, output', [ 20 | ('123', '291'), 21 | ('ff', '255'), 22 | ('abc123', '11256099') 23 | ]) 24 | def test_good_input(user_input, output, monkeypatch, capsys): 25 | monkeypatch.setattr('sys.stdin', StringIO(user_input + '\n')) 26 | hex_output() 27 | captured_out, captured_err = capsys.readouterr() 28 | assert captured_out.endswith(f'{output}\n') 29 | -------------------------------------------------------------------------------- /ch02-strings/.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | Sandbox* 3 | -------------------------------------------------------------------------------- /ch02-strings/e05_pig_latin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 5: pig_latin""" 3 | 4 | 5 | def pig_latin(word): 6 | """Translates a word into Pig Latin. 7 | The "word" parameter is assumed to be an 8 | English word, returned as a string. 9 | """ 10 | if word[0] in 'aeiou': 11 | return f'{word}way' 12 | 13 | return f'{word[1:]}{word[0]}ay' 14 | -------------------------------------------------------------------------------- /ch02-strings/e05b1_capitalized.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 5, beyond 1: Pig Latin, handle capitalization""" 3 | 4 | import string 5 | 6 | 7 | def pig_latin_capitalized(word): 8 | """Translates a word into Pig Latin. 9 | The "word" parameter is assumed to be an 10 | English word, returned as a string. 11 | 12 | If the original word is capitalized, then the output 13 | should be, as well. 14 | """ 15 | if word[0].lower() in 'aeiou': 16 | output = f'{word}way' 17 | else: 18 | output = f'{word[1:]}{word[0]}ay' 19 | 20 | if word[0] in string.ascii_uppercase: 21 | output = output.capitalize() 22 | 23 | return output 24 | -------------------------------------------------------------------------------- /ch02-strings/e05b2_punctuation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 5, beyond 2: Pig Latin, handle punctuation""" 3 | 4 | 5 | def pig_latin_punctuated(word): 6 | """Translates a word into Pig Latin. 7 | The "word" parameter is assumed to be an 8 | English word, returned as a string. 9 | 10 | If the original word ends with punctuation, then 11 | the output word ends with the same punctuation. 12 | """ 13 | punctuation = '' 14 | if word[-1] in '.?!': 15 | punctuation = word[-1] 16 | word = word[:-1] 17 | 18 | if word[0].lower() in 'aeiou': 19 | output = f'{word}way' 20 | 21 | output = f'{word[1:]}{word[0]}ay' 22 | 23 | return output + punctuation 24 | -------------------------------------------------------------------------------- /ch02-strings/e05b3_different_vowels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 5, beyond 3: Pig Latin, different vowels""" 3 | 4 | 5 | def pig_latin_multivowels(word): 6 | """Our test for moving the 7 | first letter to the end is whether 8 | the word contains two or more different vowels. 9 | """ 10 | number_of_vowels = len(set(word) & set('aeiou')) 11 | 12 | if number_of_vowels > 1: 13 | return f'{word}way' 14 | 15 | return f'{word[1:]}{word[0]}ay' 16 | -------------------------------------------------------------------------------- /ch02-strings/e06_pig_latin_sentence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 6: pl_sentence""" 3 | 4 | 5 | def pl_sentence(sentence): 6 | """Get a sentence from the user, containing 7 | lowercase, unpuncutated words. Return the 8 | sentence, translated into Pig Latin. 9 | """ 10 | output = [] 11 | for word in sentence.split(): 12 | if word[0] in 'aeiou': 13 | output.append(f'{word}way') 14 | else: 15 | output.append(f'{word[1:]}{word[0]}ay') 16 | 17 | return ' '.join(output) 18 | -------------------------------------------------------------------------------- /ch02-strings/e06b1_word_per_line.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 6, beyond 1: Word per line""" 3 | 4 | 5 | def word_per_line(filename): 6 | """Given a text file, return a sentence from the nth 7 | word for line n, for each of the first 10 lines. 8 | """ 9 | output = [] 10 | 11 | for n, one_line in enumerate(open(filename)): 12 | words = one_line.split() 13 | 14 | if not words: 15 | continue 16 | 17 | if n >= 10: 18 | break 19 | 20 | if n >= len(words): 21 | output.append(words[-1]) 22 | else: 23 | output.append(words[n]) 24 | 25 | return ' '.join(output) 26 | -------------------------------------------------------------------------------- /ch02-strings/e06b2_transpose_strings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 6, beyond 2: Transpose strings""" 3 | 4 | 5 | def transpose_strings(list_of_words): 6 | """Given a list of strings, each of which contains 7 | multiple words, transpose them. So given input of 8 | 9 | ['abc def ghi', 'jkl mno pqr', 'stu vwx yz'] 10 | 11 | the output would be 12 | 13 | ['abc jkl stu', 'def mno vwx', 'ghi pqr yz']. 14 | 15 | We assume that each of the strings contains 16 | the same number of words. 17 | 18 | """ 19 | 20 | return [' '.join(t) 21 | for t in (zip(*[s.split() 22 | for s in list_of_words]))] 23 | -------------------------------------------------------------------------------- /ch02-strings/e06b3_404_ips.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 6, beyond 3: IP addresses with 404""" 3 | 4 | 5 | def ips_for_404s(filename): 6 | """Given the name of an Apache logfile, 7 | print the IP address where the response code 8 | is 4040 9 | 10 | """ 11 | for one_line in open(filename): 12 | if ' 404 ' in one_line: 13 | print(one_line.split()[0]) 14 | -------------------------------------------------------------------------------- /ch02-strings/e07_ubbi_dubbi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 7: ubbi_dubbi""" 3 | 4 | 5 | def ubbi_dubbi(word): 6 | """Ask the user to enter a word, 7 | and return the word's translation into Ubbi Dubbi. 8 | """ 9 | output = [] 10 | for letter in word: 11 | if letter in 'aeiou': 12 | output.append(f'ub{letter}') 13 | else: 14 | output.append(letter) 15 | 16 | return ''.join(output) 17 | -------------------------------------------------------------------------------- /ch02-strings/e07b1_capital.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 7, beyond 1: Ubbi Dubbi with capitals""" 3 | 4 | import string 5 | 6 | 7 | def ubbi_dubbi_caps(word): 8 | """Ask the user to enter a word, 9 | and return the word's translation into Ubbi Dubbi. 10 | If the input word is capitalized, then the output 11 | word should be, too. 12 | """ 13 | output = [] 14 | for letter in word: 15 | if letter in 'aeiou': 16 | output.append(f'ub{letter}') 17 | else: 18 | output.append(letter) 19 | 20 | if word[0] in string.ascii_uppercase: 21 | output[0] = output[0].capitalize() 22 | 23 | return ''.join(output) 24 | -------------------------------------------------------------------------------- /ch02-strings/e07b2_remove_authors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 7, beyond 2: Remove authors' names""" 3 | 4 | import string 5 | 6 | 7 | def remove_authors_names(text, names): 8 | """Given a string (text) and a list of strings (names), 9 | remove any occurence of a name from text and return it. 10 | """ 11 | output = text 12 | 13 | for one_name in names: 14 | output = output.replace(one_name, '_' * len(one_name)) 15 | 16 | return output 17 | -------------------------------------------------------------------------------- /ch02-strings/e07b3_url_encode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 7, beyond 3: URL-encoding""" 3 | 4 | import string 5 | 6 | 7 | def url_encode(text): 8 | """Given a string, replace any character that 9 | isn't a letter or number with % and its two-digit 10 | hex code. 11 | """ 12 | safe_chars = string.ascii_letters + string.digits 13 | output = [] 14 | 15 | for one_character in text: 16 | if one_character in safe_chars: 17 | output.append(one_character) 18 | else: 19 | output.append(hex(ord(one_character)).replace('0x', '%')) 20 | 21 | return ''.join(output) 22 | -------------------------------------------------------------------------------- /ch02-strings/e08_sort_string.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 8: strsort""" 3 | 4 | 5 | def strsort(a_string): 6 | """Takes a string as input, 7 | returns a string with its characters sorted. 8 | """ 9 | return ''.join(sorted(a_string)) 10 | -------------------------------------------------------------------------------- /ch02-strings/e08b1_sort_words.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 8, beyond 1: Sort words""" 3 | 4 | 5 | def sort_words(s): 6 | """Given a string containing comma-separated 7 | words, return a string with the same words, separated 8 | by commas, but sorted. 9 | """ 10 | 11 | return ', '.join(one_word 12 | for one_word in sorted(s.split())) 13 | -------------------------------------------------------------------------------- /ch02-strings/e08b2_last_word_in_file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 8, beyond 2: Last word (alphabetically) in a file""" 3 | 4 | 5 | def last_word_alphabetically(filename): 6 | """Given the name of a text file, 7 | return the word that comes last (alphabetically) 8 | in that file. 9 | """ 10 | output = '' 11 | for one_line in open(filename): 12 | for one_word in one_line.split(): 13 | if not one_word.isalpha(): 14 | continue 15 | if one_word > output: 16 | output = one_word 17 | return output 18 | -------------------------------------------------------------------------------- /ch02-strings/e08b3_longest_word_in_file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 2, exercise 8, beyond 3: Longest word in a file""" 3 | 4 | 5 | def longest_word(filename): 6 | """Given the name of a text file, 7 | return the longest word in that file. 8 | """ 9 | output = '' 10 | for one_line in open(filename): 11 | for one_word in one_line.split(): 12 | if not one_word.isalpha(): 13 | continue 14 | if len(one_word) > len(output): 15 | output = one_word 16 | return output 17 | -------------------------------------------------------------------------------- /ch02-strings/test_e05_pig_latin.py: -------------------------------------------------------------------------------- 1 | from e05_pig_latin import pig_latin 2 | from io import StringIO 3 | import pytest 4 | 5 | 6 | @pytest.mark.parametrize('input_word, output_word', [ 7 | ('computer', 'omputercay'), 8 | ('table', 'abletay'), 9 | ('papaya', 'apayapay'), 10 | ('elephant', 'elephantway'), 11 | ('octopus', 'octopusway'), 12 | ('insightful', 'insightfulway')]) 13 | def test_simple(input_word, output_word): 14 | assert pig_latin(input_word) == output_word 15 | -------------------------------------------------------------------------------- /ch02-strings/test_e06_pig_latin_sentence.py: -------------------------------------------------------------------------------- 1 | from e06_pig_latin_sentence import pl_sentence 2 | from io import StringIO 3 | import pytest 4 | 5 | 6 | @pytest.mark.parametrize('input_words, output_words', [ 7 | ('papaya', 'apayapay'), 8 | ('elephant', 'elephantway'), 9 | ('this is a test', 'histay isway away esttay'), 10 | ('python is the best language ever', 'ythonpay isway hetay estbay anguagelay everway')]) 11 | def test_simple(input_words, output_words): 12 | assert pl_sentence(input_words) == output_words 13 | -------------------------------------------------------------------------------- /ch02-strings/test_e07_ubbi_dubbi.py: -------------------------------------------------------------------------------- 1 | from e07_ubbi_dubbi import ubbi_dubbi 2 | from io import StringIO 3 | import pytest 4 | 5 | 6 | @pytest.mark.parametrize('input_word, output_word', [ 7 | ('papaya', 'pubapubayuba'), 8 | ('elephant', 'ubelubephubant'), 9 | ('testing', 'tubestubing'), 10 | ('banana', 'bubanubanuba'), 11 | ]) 12 | def test_simple(input_word, output_word): 13 | assert ubbi_dubbi(input_word) == output_word 14 | -------------------------------------------------------------------------------- /ch02-strings/test_e08_sort_string.py: -------------------------------------------------------------------------------- 1 | from e08_sort_string import strsort 2 | from io import StringIO 3 | import pytest 4 | 5 | 6 | @pytest.mark.parametrize('input_word, output_word', [ 7 | ('abcdef', 'abcdef'), 8 | ('abcDEF', 'DEFabc'), 9 | ('ab c', ' abc'), 10 | ('abcdefabcdef', 'aabbccddeeff') 11 | ]) 12 | def test_strsort(input_word, output_word): 13 | assert strsort(input_word) == output_word 14 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e09_firstlast.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 9: firstlast""" 3 | 4 | 5 | def firstlast(sequence): 6 | """Given a sequence, returns a two-element sequence. 7 | The returned sequence will be of the same type as the argument. 8 | Its two elements will be the argument's first and last elements. 9 | """ 10 | return sequence[:1] + sequence[-1:] 11 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e09b1_square_all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 9, beyond 1: Square integers and floats""" 3 | 4 | 5 | def square(n): 6 | """Takes a number (integer or float) and returns 7 | its square -- i.e., the number to the 2nd power 8 | """ 9 | return n ** 2 10 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e09b2_largest_element.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 9, beyond 2: Largest anything""" 3 | 4 | 5 | def largest(s): 6 | """Takes a sequence, and returns the largest 7 | element (as defined by >) from the sequence. 8 | """ 9 | if not s: 10 | return None 11 | 12 | output = s[0] 13 | 14 | for one_item in s[1:]: 15 | if one_item > output: 16 | output = one_item 17 | 18 | return output 19 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e09b3_largest_word.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 9, beyond 3: Longest word in file-like""" 3 | 4 | 5 | def longest_word(f): 6 | """Takes a file-like object, and returns the longest 7 | word it finds. 8 | """ 9 | longest_word = '' 10 | 11 | for one_line in f: 12 | for one_word in one_line.split(): 13 | if len(one_word) > len(longest_word): 14 | longest_word = one_word 15 | 16 | return longest_word 17 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e09b4_even_odd_sums.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 9, beyond 4: even_odd_sums""" 3 | 4 | 5 | def even_odd_sums(numbers): 6 | """Takes a list of numbers, and returns a two-element 7 | list containing the sum of the even elements and the 8 | sum of the odd elements. 9 | """ 10 | evens = [] 11 | odds = [] 12 | 13 | for one_number in numbers: 14 | if one_number % 2: 15 | odds.append(one_number) 16 | else: 17 | evens.append(one_number) 18 | 19 | return [sum(evens), sum(odds)] 20 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e09b5_plus_minus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 9, beyond 5: plus_minus""" 3 | 4 | 5 | def plus_minus(numbers): 6 | """Takes a list of numbers, and returns the result 7 | of alternately adding and subtracting them. 8 | """ 9 | 10 | if not numbers: 11 | return 0 12 | 13 | total = numbers.pop(0) 14 | 15 | while numbers: 16 | total += numbers.pop(0) 17 | 18 | if numbers: 19 | total -= numbers.pop(0) 20 | 21 | return total 22 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e09b6_myzip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 9, beyond 6: myzip""" 3 | 4 | 5 | def myzip(*args): 6 | """Takes any number of iterables, returning 7 | a list of tuples. The tuple at index n will contain 8 | the items from index n in each iterable. We can 9 | assume that all of the iterables have the same length. 10 | """ 11 | return [tuple(a[i] for a in args) 12 | for i in range(len(min(args, key=len)))] 13 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e10_sum_anything.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 10: mysum""" 3 | 4 | 5 | def mysum(*items): 6 | """Sum the passed arguments, which should be of the same type. 7 | The arguments should handle the + operator. 8 | If passed no arguments, then return an empty tuple. 9 | """ 10 | if not items: 11 | return items 12 | output = items[0] 13 | for item in items[1:]: 14 | output += item 15 | return output 16 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e10b1_mysum_bigger_than.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 10, beyond 1: mysum_bigger_than""" 3 | 4 | 5 | def mysum_bigger_than(threshold, *items): 6 | """Sum items, which should be of the same type. 7 | Ignore any below the value of threshold. 8 | The arguments should handle the + operator. 9 | If passed no arguments, then return an empty tuple. 10 | """ 11 | if not items: 12 | return items 13 | output = 0 14 | for item in items: 15 | if item > threshold: 16 | output += item 17 | return output 18 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e10b2_sum_numeric.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 10, beyond 2: sum_numeric""" 3 | 4 | 5 | def sum_numeric(items): 6 | """Sum all items, assuming that they 7 | are integers or can be turned into integers. 8 | """ 9 | total = 0 10 | 11 | for item in items: 12 | try: 13 | total += int(item) 14 | except ValueError: 15 | pass 16 | return total 17 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e10b3_combine_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 10, beyond 3: combine_dicts""" 3 | 4 | 5 | def combine_dicts(*args): 6 | """Return a dict, the result of combining all 7 | elements of args (which should be dicts). If a key 8 | occurs in more than one, then the value should be a list 9 | containing all values from the arguments. 10 | """ 11 | output = {} 12 | 13 | for d in args: 14 | for key, value in d.items(): 15 | if key in output: 16 | try: 17 | output[key].append(value) 18 | except AttributeError: 19 | output[key] = [output[key], value] 20 | else: 21 | output[key] = value 22 | 23 | return output 24 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e11_alphabetize_names.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 11: alphabetize_names""" 3 | 4 | import operator 5 | 6 | 7 | PEOPLE = [{'first': 'Reuven', 'last': 'Lerner', 8 | 'email': 'reuven@lerner.co.il'}, 9 | {'first': 'Donald', 'last': 'Trump', 10 | 'email': 'president@whitehouse.gov'}, 11 | {'first': 'Vladimir', 'last': 'Putin', 12 | 'email': 'president@kremvax.ru'} 13 | ] 14 | 15 | 16 | def alphabetize_names(list_of_dicts): 17 | """Take a list of dicts describing people, 18 | each with first/last/email as keys. 19 | 20 | Return a new list of dicts, 21 | sorted first by last name and then by first name. 22 | 23 | If passed an empty list, then return an empty list. 24 | """ 25 | return sorted(list_of_dicts, key=operator.itemgetter('last', 'first')) 26 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e11b1_sort_absolute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 11, beyond 1: sort_absolute""" 3 | 4 | 5 | def sort_absolute(numbers): 6 | """Given an iterable of numbers, return 7 | a list of those numbers sorted by absolute value. 8 | """ 9 | return sorted(numbers, key=abs) 10 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e11b2_sort_by_vowel_count.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 11, beyond 2: sort_by_vowel_count""" 3 | 4 | 5 | def by_vowel_count(one_word): 6 | total = 0 7 | for one_character in one_word.lower(): 8 | if one_character in 'aeiou': 9 | total += 1 10 | return total 11 | 12 | 13 | def sort_by_vowel_count(words): 14 | """Given a list of strings (words), return 15 | a list of those words sorted by the number of vowels 16 | they contain. 17 | """ 18 | return sorted(words, key=by_vowel_count) 19 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e11b3_sort_by_sum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 11, beyond 3: sort_by_vowel_sum""" 3 | 4 | 5 | def sort_by_sum(list_of_lists): 6 | """Given a list of lists, in which the inner lists contain 7 | numbers, return the outer list sorted by each inner list's sum. 8 | """ 9 | return sorted(list_of_lists, key=sum) 10 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e12_most_repeating_letters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 12: most_repeating_word""" 3 | 4 | from collections import Counter 5 | 6 | WORDS = ['this', 'is', 'an', 'elementary', 'test', 'example'] 7 | 8 | 9 | def most_repeating_letter_count(word): 10 | """Given a non-empty string, counts how 11 | many times each letter appears in the string, 12 | and returns an integer indicating how often 13 | the most common letter appears.""" 14 | return Counter(word).most_common(1)[0][1] 15 | 16 | 17 | def most_repeating_word(words): 18 | """Given a list of non-empty strings (words), 19 | returns the word containing at least one letter that repeats 20 | more often than any letter in any other word. 21 | 22 | Because sorting in Python is stable, if multiple words have 23 | the same count, then the first will be returned.""" 24 | return max(words, key=most_repeating_letter_count) 25 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e12b1_most_repeated_vowels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 12, beyond 1: most_repeating_vowels""" 3 | 4 | from collections import Counter 5 | 6 | WORDS = ['this', 'is', 'an', 'elementary', 'test', 'example'] 7 | 8 | 9 | def most_repeating_vowel_count(word): 10 | vowels_in_word = '' 11 | for one_character in word.lower(): 12 | if one_character in 'aeiou': 13 | vowels_in_word += one_character 14 | 15 | return Counter(vowels_in_word).most_common(1)[0][1] 16 | 17 | 18 | def most_repeating_word(words): 19 | return max(words, key=most_repeating_vowel_count) 20 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e12b2_popular_shells.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 12, beyond 2: shells_by_popularity""" 3 | 4 | from collections import Counter 5 | import operator 6 | 7 | 8 | def shells_by_popularity(filename): 9 | shells = Counter(one_line.split(':')[-1].strip() 10 | for one_line in open(filename) 11 | if not one_line.startswith(('#', '\n'))) 12 | 13 | return sorted(shells.items(), 14 | key=operator.itemgetter(1), reverse=True) 15 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e12b3_shells_and_users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 12, beyond 3: shells_and_users""" 3 | 4 | from collections import defaultdict 5 | 6 | 7 | def shells_and_users_by_popularity(filename): 8 | shells = defaultdict(list) 9 | for one_line in open(filename): 10 | if one_line.startswith(('#', '\n')): 11 | continue 12 | 13 | username, *rest, shell = one_line.strip().split(':') 14 | 15 | shells[shell].append(username) 16 | 17 | return sorted(shells.items(), key=len) 18 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e13_tuple_records.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 13: tuple_records""" 3 | 4 | 5 | import operator 6 | PEOPLE = [('Donald', 'Trump', 7.85), 7 | ('Vladimir', 'Putin', 3.626), 8 | ('Jinping', 'Xi', 10.603)] 9 | 10 | 11 | def format_sort_records(list_of_tuples): 12 | """This function expects to get a list 13 | of tuples, each representing a person. 14 | 15 | Each tuple contains three elements -- first 16 | name, last name, and distance to travel. 17 | 18 | (The first two are strings, and the third is 19 | a float.) We return a list of strings, 20 | sorted by last name and then first name. 21 | """ 22 | output = [] 23 | template = '{1:10} {0:10} {2:5.2f}' 24 | for person in sorted(list_of_tuples, key=operator.itemgetter(1, 0)): 25 | output.append(template.format(*person)) 26 | return output 27 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e13b1_namedtuple_records.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 13, beyond 1: namedtuple_records""" 3 | 4 | 5 | import operator 6 | from collections import namedtuple 7 | 8 | Person = namedtuple('Person', ['first', 'last', 'distance']) 9 | 10 | 11 | PEOPLE = [Person('Donald', 'Trump', 7.85), 12 | Person('Vladimir', 'Putin', 3.626), 13 | Person('Jinping', 'Xi', 10.603)] 14 | 15 | 16 | def format_sort_records(list_of_tuples): 17 | output = [] 18 | template = '{last:10} {first:10} {distance:5.2f}' 19 | for person in sorted(list_of_tuples, key=operator.attrgetter('last', 'first')): 20 | output.append(template.format(*(person._asdict()))) 21 | return output 22 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e13b2_sorted_movies.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 13, beyond 2: sorted_movies""" 3 | 4 | 5 | import operator 6 | from collections import namedtuple 7 | 8 | MOVIES = [('Parasite', 132, 'Bong Joon-ho'), 9 | ('Ford v Ferrari', 152, 'James Mangold'), 10 | ('The Irishman', 209, 'Martin Scorsese'), 11 | ('Jojo Rabbit', 108, 'Taika Waititi'), 12 | ('Joker', 122, 'Todd Phillips'), 13 | ('Little Women', 135, 'Greta Gerwig'), 14 | ('Marriage Story', 137, 'Noah Baumbach'), 15 | ('1917', 119, 'Sam Mendes'), 16 | ('Once Upon a Time in Hollywood', 161, 'Quentin Tarantino') 17 | ] 18 | 19 | FIELDS = {'name': 0, 20 | 'length': 1, 21 | 'director': 2} 22 | 23 | 24 | def sort_movies(): 25 | sort_by = input("Enter sort field (name/length/director): ").strip() 26 | 27 | if sort_by in FIELDS: 28 | 29 | output = [] 30 | template = '{0:30} {1:3} {2:20}' 31 | for one_movie in sorted(MOVIES, key=operator.itemgetter(FIELDS[sort_by])): 32 | output.append(template.format(*one_movie)) 33 | return output 34 | 35 | print(f'No such field {sort_by}') 36 | -------------------------------------------------------------------------------- /ch03-lists-tuples/e13b3_sorted_movies_multifield.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 3, exercise 13, beyond 3: sorted_movies_multifield""" 3 | 4 | 5 | import operator 6 | from collections import namedtuple 7 | 8 | MOVIES = [('Parasite', 132, 'Bong Joon-ho'), 9 | ('Ford v Ferrari', 152, 'James Mangold'), 10 | ('The Irishman', 209, 'Martin Scorsese'), 11 | ('Jojo Rabbit', 108, 'Taika Waititi'), 12 | ('Joker', 122, 'Todd Phillips'), 13 | ('Little Women', 135, 'Greta Gerwig'), 14 | ('Marriage Story', 137, 'Noah Baumbach'), 15 | ('1917', 119, 'Sam Mendes'), 16 | ('Once Upon a Time in Hollywood', 161, 'Quentin Tarantino') 17 | ] 18 | 19 | FIELDS = {'name': 0, 20 | 'length': 1, 21 | 'director': 2} 22 | 23 | 24 | def sort_movies_multifield(): 25 | """The user can enter more than one sort field, separated by whitespace""" 26 | 27 | sort_by = input("Enter sort fields (name/length/director): ").split() 28 | 29 | if all(field_name in FIELDS 30 | for field_name in sort_by): 31 | 32 | output = [] 33 | template = '{0:30} {1:3} {2:20}' 34 | for one_movie in sorted(MOVIES, key=operator.itemgetter(*[FIELDS[field_name] 35 | for field_name in sort_by])): 36 | output.append(template.format(*one_movie)) 37 | return output 38 | 39 | print(f'No such field {sort_by}') 40 | -------------------------------------------------------------------------------- /ch03-lists-tuples/test_e09_firstlast.py: -------------------------------------------------------------------------------- 1 | from e09_firstlast import firstlast 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('arg, output', [ 6 | ('abcd', 'ad'), 7 | ([10, 20, 30, 40, 50], [10, 50]), 8 | ((10, 20, 30, 40), (10, 40)), 9 | ('ab', 'ab'), 10 | ('a', 'aa'), 11 | ([10], [10, 10]) 12 | ]) 13 | def test_firstlast(arg, output): 14 | assert firstlast(arg) == output 15 | -------------------------------------------------------------------------------- /ch03-lists-tuples/test_e10_sum_anything.py: -------------------------------------------------------------------------------- 1 | from e10_sum_anything import mysum 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('args, output', [ 6 | ((), ()), 7 | ((10, 20, 30), 60), 8 | (('a', 'b', 'c'), 'abc'), 9 | ([10, 20, 30], 60) 10 | ]) 11 | def test_mysum(args, output): 12 | assert mysum(*args) == output 13 | -------------------------------------------------------------------------------- /ch03-lists-tuples/test_e11_alphabetize_names.py: -------------------------------------------------------------------------------- 1 | from e11_alphabetize_names import alphabetize_names, PEOPLE 2 | 3 | 4 | def test_empty(): 5 | assert alphabetize_names([]) == [] 6 | 7 | 8 | def test_with_people(): 9 | assert PEOPLE[0]['last'] == 'Lerner' 10 | assert PEOPLE[1]['last'] == 'Trump' 11 | assert PEOPLE[2]['last'] == 'Putin' 12 | 13 | output = alphabetize_names(PEOPLE) 14 | assert output[0]['last'] == 'Lerner' 15 | assert output[1]['last'] == 'Putin' 16 | assert output[2]['last'] == 'Trump' 17 | -------------------------------------------------------------------------------- /ch03-lists-tuples/test_e12_most_repeating_letters.py: -------------------------------------------------------------------------------- 1 | from e12_most_repeating_letters import most_repeating_letter_count, most_repeating_word, WORDS 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('one_word, count', [ 6 | ('banana', 3), 7 | ('apple', 2), 8 | ('balloon', 2), 9 | ('abcabcabca', 4) 10 | ]) 11 | def test_most_repeating_letter_count(one_word, count): 12 | assert most_repeating_letter_count(one_word) == count 13 | 14 | 15 | @pytest.mark.parametrize('list_of_words, one_word', [ 16 | (WORDS, 'elementary'), 17 | ('hello out there'.split(), 'hello'), 18 | ('there out hello'.split(), 'there') 19 | ]) 20 | def test_most_repeating_word(list_of_words, one_word): 21 | assert most_repeating_word(list_of_words) == one_word 22 | -------------------------------------------------------------------------------- /ch03-lists-tuples/test_e13_tuple_records.py: -------------------------------------------------------------------------------- 1 | from e13_tuple_records import format_sort_records, PEOPLE 2 | 3 | 4 | def test_empty(): 5 | assert format_sort_records([]) == [] 6 | 7 | 8 | def test_with_people(): 9 | output = format_sort_records(PEOPLE) 10 | assert isinstance(output, list) 11 | assert all(isinstance(x, str) for x in output) 12 | 13 | assert output[0][:10].strip() == 'Putin' 14 | assert output[0][10:20].strip() == 'Vladimir' 15 | assert output[0][20:].strip() == '3.63' 16 | -------------------------------------------------------------------------------- /ch04-dicts/.ipynb_checkpoints/Chapter 4 — Dicts and sets-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 2 6 | } 7 | -------------------------------------------------------------------------------- /ch04-dicts/.ipynb_checkpoints/Chapter 5 — Files-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Exercise 18: Final line\n", 8 | "\n", 9 | "Given a filename, return the final line in that file." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "data": { 19 | "text/plain": [ 20 | "'_driverkit:*:270:270:DriverKit:/var/empty:/usr/bin/false\\n'" 21 | ] 22 | }, 23 | "execution_count": 1, 24 | "metadata": {}, 25 | "output_type": "execute_result" 26 | } 27 | ], 28 | "source": [ 29 | "def get_final_line(filename):\n", 30 | " final_line = ''\n", 31 | " for one_line in open(filename):\n", 32 | " final_line = one_line\n", 33 | " return final_line\n", 34 | "\n", 35 | "get_final_line('/etc/passwd')\n", 36 | " " 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 2, 42 | "metadata": {}, 43 | "outputs": [ 44 | { 45 | "name": "stdout", 46 | "output_type": "stream", 47 | "text": [ 48 | "_driverkit:*:270:270:DriverKit:/var/empty:/usr/bin/false\r\n" 49 | ] 50 | } 51 | ], 52 | "source": [ 53 | "!tail -1 /etc/passwd" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [] 62 | } 63 | ], 64 | "metadata": { 65 | "kernelspec": { 66 | "display_name": "Python 3", 67 | "language": "python", 68 | "name": "python3" 69 | }, 70 | "language_info": { 71 | "codemirror_mode": { 72 | "name": "ipython", 73 | "version": 3 74 | }, 75 | "file_extension": ".py", 76 | "mimetype": "text/x-python", 77 | "name": "python", 78 | "nbconvert_exporter": "python", 79 | "pygments_lexer": "ipython3", 80 | "version": "3.7.7" 81 | } 82 | }, 83 | "nbformat": 4, 84 | "nbformat_minor": 2 85 | } 86 | -------------------------------------------------------------------------------- /ch04-dicts/e14_restaurant.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 14: restaurant""" 3 | 4 | 5 | MENU = {'sandwich': 10, 'tea': 7, 'salad': 9} 6 | 7 | 8 | def restaurant(): 9 | """Ask the user to enter their dining preferences, one by one, based 10 | on the global "MENU" dict. 11 | 12 | - If the user enters an empty string, stop asking and print the total bill. 13 | - If the user enters something on the menu (i.e., a key in "MENU"), then 14 | print the price and the total. 15 | - If the user enters something not on the menu, then tell them the item isn't 16 | available. 17 | """ 18 | 19 | total = 0 20 | while True: 21 | order = input('Order: ').strip() 22 | 23 | if not order: 24 | break 25 | 26 | if order in MENU: 27 | price = MENU[order] 28 | total += price 29 | print(f'{order} costs {price}, total is {total}') 30 | 31 | else: 32 | print(f'We are fresh out of {order} today') 33 | 34 | print(f'Your total is {total}') 35 | -------------------------------------------------------------------------------- /ch04-dicts/e14b1_simple_login.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 14: beyond 1: simple_login""" 3 | 4 | 5 | USERS = {'root': '1234', 'ceo': '!!!!!', 'reuven': 'GrEaTpW?'} 6 | 7 | 8 | def login(): 9 | while True: 10 | username = input("Enter your username: ").strip() 11 | password = input("Enter your password: ").strip() 12 | 13 | if USERS.get(username) == password: 14 | return True 15 | 16 | else: 17 | return False 18 | -------------------------------------------------------------------------------- /ch04-dicts/e14b2_temps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 14: beyond 2: temps""" 3 | 4 | from datetime import datetime, timedelta 5 | 6 | 7 | TEMPS = {'2020-08-16': 30, 8 | '2020-08-17': 32, 9 | '2020-08-18': 32, 10 | 11 | } 12 | 13 | 14 | def temps(): 15 | while True: 16 | today = input("Enter date in YYYY-MM-DD format:").strip() 17 | 18 | if not today: 19 | break 20 | 21 | if len(today) != 10: 22 | print(f'Invalid format; try again. ') 23 | continue 24 | 25 | if today.count('-') != 2: 26 | print(f'Invalid format; try again. ') 27 | continue 28 | 29 | if today not in TEMPS: 30 | print(f'The date {today} is unknown; try again. ') 31 | continue 32 | 33 | try: 34 | today_date = datetime.fromisoformat(today).date() 35 | except ValueError as e: 36 | print(f'Not a valid date string; try again. ') 37 | continue 38 | 39 | yesterday_date = str(today_date - timedelta(1)) 40 | tomorrow_date = str(today_date + timedelta(1)) 41 | 42 | print(f'{yesterday_date}: {TEMPS.get(yesterday_date, "No data available")}') 43 | print(f'{today_date}: {TEMPS[str(today_date)]}') 44 | print(f'{tomorrow_date}: {TEMPS.get(tomorrow_date, "No data available")}') 45 | -------------------------------------------------------------------------------- /ch04-dicts/e14b3_days_old.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 14: beyond 3: days_old""" 3 | 4 | from datetime import date 5 | 6 | 7 | PEOPLE = {'Reuven': date.fromisoformat('1970-07-14'), 8 | 'Atara': date.fromisoformat('2000-12-16'), 9 | 'Shikma': date.fromisoformat('2002-12-17'), 10 | 'Amotz': date.fromisoformat('2005-10-31') 11 | } 12 | 13 | 14 | def calculate_days(): 15 | while True: 16 | name = input("Enter a person's name: ").strip() 17 | 18 | if not name: 19 | break 20 | 21 | today = date.today() 22 | 23 | if name in PEOPLE: 24 | print(f'{name} is {(today - PEOPLE[name]).days}') 25 | else: 26 | print(f'{name} is not in the system.') 27 | -------------------------------------------------------------------------------- /ch04-dicts/e15_rain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 15: rainfall""" 3 | 4 | 5 | def get_rainfall(): 6 | """Ask the user repeatedly for a city name and mm of rainfall. 7 | 8 | If the city is blank, then stop asking questions, 9 | and report all cities and rainfall. 10 | 11 | Otherwise, ask for rainfall and add the current rainfall 12 | to any previous report for that city. 13 | """ 14 | 15 | rainfall = {} 16 | 17 | while True: 18 | city_name = input('Enter city name: ') 19 | if not city_name: 20 | break 21 | 22 | mm_rain = input('Enter mm rain: ') 23 | rainfall[city_name] = rainfall.get(city_name, 0) + int(mm_rain) 24 | 25 | for city, rain in rainfall.items(): 26 | print(f'{city}: {rain}') 27 | -------------------------------------------------------------------------------- /ch04-dicts/e15b1_average.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 15, beyond 1: rainfall with averages""" 3 | 4 | 5 | def get_rainfall(): 6 | rainfall = {} 7 | 8 | while True: 9 | city_name = input('Enter city name: ') 10 | if not city_name: 11 | break 12 | 13 | mm_rain = input('Enter mm rain: ') 14 | 15 | if city_name not in rainfall: 16 | rainfall[city_name] = [] 17 | 18 | rainfall[city_name].append(int(mm_rain)) 19 | 20 | for city, rain in rainfall.items(): 21 | print(f'{city}: Total {sum(rain)}, Average {sum(rain)/len(rain)}') 22 | -------------------------------------------------------------------------------- /ch04-dicts/e15b2_error_ips.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 15, beyond 2: errors with IP addresses""" 3 | 4 | from collections import defaultdict 5 | 6 | 7 | def error_ip_addresses(filename): 8 | output = defaultdict(list) 9 | 10 | for one_line in open(filename): 11 | fields = one_line.split() 12 | ip_address = fields[0] 13 | response_code = fields[8] 14 | output[response_code].append(ip_address) 15 | 16 | return output 17 | -------------------------------------------------------------------------------- /ch04-dicts/e15b3_word_lengths.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 15, beyond 3: word lengths""" 3 | 4 | from collections import defaultdict 5 | 6 | 7 | def word_lengths(filename): 8 | output = defaultdict(int) 9 | 10 | for one_line in open(filename): 11 | for one_word in one_line.split(): 12 | output[len(one_word)] += 1 13 | 14 | return output 15 | -------------------------------------------------------------------------------- /ch04-dicts/e16_dictdiff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 16: dictdiff""" 3 | 4 | 5 | def dictdiff(first, second): 6 | """Accepts two dicts as arguments. 7 | Returns a dict describing the differences between the two arguments. 8 | Each key-value pair in the returned dict represents a difference. Each 9 | difference consists of a key and a two-element list, indicating the 10 | values from the two input dicts. If a key exists in one dict but not 11 | another, then the corresponding value will be None. 12 | """ 13 | output = {} 14 | all_keys = first.keys() | second.keys() 15 | 16 | for key in all_keys: 17 | if first.get(key) != second.get(key): 18 | output[key] = [first.get(key), second.get(key)] 19 | return output 20 | -------------------------------------------------------------------------------- /ch04-dicts/e16b1_multiupdate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 16, beyond 1: multi_update""" 3 | 4 | 5 | def multi_update(*args): 6 | output = {} 7 | 8 | for one_dict in args: 9 | output.update(one_dict) 10 | 11 | return output 12 | -------------------------------------------------------------------------------- /ch04-dicts/e16b2_dict_from_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 16, beyond 2: dict_from_list""" 3 | 4 | 5 | def dict_from_list(*args): 6 | if len(args) % 2: 7 | raise ValueError('Need an even number of args') 8 | 9 | output = {} 10 | 11 | while args: 12 | output[args[0]] = args[1] 13 | args = args[2:] 14 | 15 | return output 16 | -------------------------------------------------------------------------------- /ch04-dicts/e16b3_partition_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 16, beyond 3: partition_dict""" 3 | 4 | 5 | def partition_dict(d, f): 6 | output_true = {} 7 | output_false = {} 8 | 9 | for key, value in d.items(): 10 | if f(key, value): 11 | output_true[key] = value 12 | else: 13 | output_false[key] = value 14 | 15 | return output_true, output_false 16 | -------------------------------------------------------------------------------- /ch04-dicts/e17_different_numbers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 17: different_numbers""" 3 | 4 | 5 | def how_many_different_numbers(numbers): 6 | """Takes a list of numbers as input. 7 | Returns the number of different numbers in that list. 8 | """ 9 | unique_numbers = set(numbers) 10 | return len(unique_numbers) 11 | -------------------------------------------------------------------------------- /ch04-dicts/e17b1_different_ips.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 17, beyond 1: different_ips""" 3 | 4 | 5 | def different_ips(filename): 6 | return {one_line.split()[0] 7 | for one_line in open(filename)} 8 | -------------------------------------------------------------------------------- /ch04-dicts/e17b2_different_responses.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 17, beyond 2: different_responses""" 3 | 4 | 5 | def different_responses(filename): 6 | return {one_line.split()[8] 7 | for one_line in open(filename)} 8 | -------------------------------------------------------------------------------- /ch04-dicts/e17b3_different_extensions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 4, exercise 17, beyond 3: different_extensions""" 3 | 4 | import os 5 | 6 | 7 | def different_extensions(dirname): 8 | return {os.path.splitext(one_filename)[-1] 9 | for one_filename in os.listdir(dirname)} 10 | -------------------------------------------------------------------------------- /ch04-dicts/test_e14_restaurant.py: -------------------------------------------------------------------------------- 1 | from e14_restaurant import restaurant 2 | import pytest 3 | from io import StringIO 4 | 5 | 6 | def test_order_nothing(monkeypatch, capsys): 7 | monkeypatch.setattr('sys.stdin', StringIO('\n')) 8 | restaurant() 9 | captured_out, captured_err = capsys.readouterr() 10 | assert captured_out.endswith('Your total is 0\n') 11 | 12 | 13 | def test_order_one_entree(monkeypatch, capsys): 14 | monkeypatch.setattr('sys.stdin', StringIO('sandwich\n\n')) 15 | restaurant() 16 | captured_out, captured_err = capsys.readouterr() 17 | assert 'sandwich costs 10, total is 10' in captured_out 18 | assert captured_out.endswith('Your total is 10\n') 19 | 20 | 21 | def test_order_two_entrees(monkeypatch, capsys): 22 | monkeypatch.setattr('sys.stdin', StringIO('sandwich\nsandwich\n\n')) 23 | restaurant() 24 | captured_out, captured_err = capsys.readouterr() 25 | assert 'sandwich costs 10, total is 10' in captured_out 26 | assert 'sandwich costs 10, total is 20' in captured_out 27 | assert captured_out.endswith('Your total is 20\n') 28 | 29 | 30 | def test_order_many_items(monkeypatch, capsys): 31 | monkeypatch.setattr('sys.stdin', StringIO( 32 | 'sandwich\nsandwich\nsalad\nelephant\ntea\n\n')) 33 | restaurant() 34 | captured_out, captured_err = capsys.readouterr() 35 | assert 'sandwich costs 10, total is 10' in captured_out 36 | assert 'sandwich costs 10, total is 20' in captured_out 37 | assert 'salad costs 9, total is 29' in captured_out 38 | assert 'We are fresh out of elephant today' in captured_out 39 | assert 'tea costs 7, total is 36' in captured_out 40 | assert captured_out.endswith('Your total is 36\n') 41 | -------------------------------------------------------------------------------- /ch04-dicts/test_e15_rain.py: -------------------------------------------------------------------------------- 1 | from e15_rain import get_rainfall 2 | import pytest 3 | from io import StringIO 4 | 5 | 6 | def test_nothing(monkeypatch, capsys): 7 | monkeypatch.setattr('sys.stdin', StringIO('\n')) 8 | get_rainfall() 9 | captured_out, captured_err = capsys.readouterr() 10 | assert captured_out.strip() == 'Enter city name:' 11 | 12 | 13 | def test_one_city(monkeypatch, capsys): 14 | monkeypatch.setattr('sys.stdin', StringIO('Tel Aviv\n5\n\n')) 15 | get_rainfall() 16 | captured_out, captured_err = capsys.readouterr() 17 | assert captured_out.endswith('Tel Aviv: 5\n') 18 | 19 | 20 | def test_two_cities(monkeypatch, capsys): 21 | monkeypatch.setattr('sys.stdin', StringIO('Tel Aviv\n5\nJerusalem\n3\n\n')) 22 | get_rainfall() 23 | captured_out, captured_err = capsys.readouterr() 24 | assert captured_out.endswith('Tel Aviv: 5\nJerusalem: 3\n') 25 | 26 | 27 | def test_repeat_city(monkeypatch, capsys): 28 | monkeypatch.setattr('sys.stdin', StringIO( 29 | 'Tel Aviv\n5\nJerusalem\n3\nTel Aviv\n7\n\n')) 30 | get_rainfall() 31 | captured_out, captured_err = capsys.readouterr() 32 | assert captured_out.endswith('Tel Aviv: 12\nJerusalem: 3\n') 33 | 34 | 35 | def test_no_checking_of_rain_input(monkeypatch, capsys): 36 | with pytest.raises(ValueError): 37 | monkeypatch.setattr('sys.stdin', StringIO('Tel Aviv\nabc\n\n')) 38 | get_rainfall() 39 | -------------------------------------------------------------------------------- /ch04-dicts/test_e16_dictdiff.py: -------------------------------------------------------------------------------- 1 | from e16_dictdiff import dictdiff 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def simple_dict1(): 7 | return {'a': 1, 'b': 2, 'c': 3} 8 | 9 | 10 | @pytest.fixture 11 | def simple_dict2(): 12 | return {'a': 1, 'b': 2, 'c': 4} 13 | 14 | 15 | @pytest.fixture 16 | def simple_dict3(): 17 | return {'a': 1, 'b': 2, 'd': 3} 18 | 19 | 20 | def test_empty(): 21 | assert dictdiff({}, {}) == {} 22 | 23 | 24 | def test_same(simple_dict1): 25 | assert dictdiff(simple_dict1, simple_dict1) == {} 26 | 27 | 28 | def test_simple_diff1(simple_dict1, simple_dict2): 29 | assert dictdiff(simple_dict1, simple_dict2) == {'c': [3, 4]} 30 | 31 | 32 | def test_simple_diff2(simple_dict1, simple_dict3): 33 | assert dictdiff(simple_dict1, simple_dict3) == { 34 | 'c': [3, None], 'd': [None, 3]} 35 | 36 | 37 | def test_simple_diff_bw(simple_dict1, simple_dict3): 38 | assert dictdiff(simple_dict3, simple_dict1) == { 39 | 'c': [None, 3], 'd': [3, None]} 40 | -------------------------------------------------------------------------------- /ch04-dicts/test_e17_different_numbers.py: -------------------------------------------------------------------------------- 1 | from e17_different_numbers import how_many_different_numbers 2 | import pytest 3 | 4 | 5 | def test_empty(): 6 | assert how_many_different_numbers([]) == 0 7 | 8 | 9 | def test_all_different(): 10 | assert how_many_different_numbers([10, 20, 30]) == 3 11 | 12 | 13 | def test_all_same(): 14 | assert how_many_different_numbers([10, 10, 10]) == 1 15 | 16 | 17 | def test_same_even_if_float(): 18 | assert how_many_different_numbers([10, 10.0, 10]) == 1 19 | -------------------------------------------------------------------------------- /ch05-files/.ipynb_checkpoints/Untitled-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 4 6 | } 7 | -------------------------------------------------------------------------------- /ch05-files/e18_final_line.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 18: get_final_line""" 3 | 4 | 5 | def get_final_line(filename): 6 | """Given a filename, returns the final line in that file.""" 7 | final_line = '' 8 | for current_line in open(filename): 9 | final_line = current_line 10 | return final_line 11 | -------------------------------------------------------------------------------- /ch05-files/e18b1_sum_ints.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 18, beyond 1: sum_ints""" 3 | 4 | 5 | def sum_ints(filename): 6 | total = 0 7 | 8 | for one_line in open(filename): 9 | for one_word in one_line.split(): 10 | if one_word.isdigit(): 11 | total += int(one_word) 12 | 13 | return total 14 | -------------------------------------------------------------------------------- /ch05-files/e18b2_sum_mult_columns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 18, beyond 2: sum_mult_columns""" 3 | 4 | 5 | def sum_mult_columns(filename): 6 | total = 0 7 | 8 | for one_line in open(filename): 9 | fields = one_line.split() 10 | 11 | if len(fields) != 2: 12 | continue 13 | 14 | first, second = fields 15 | 16 | if not first.isdigit(): 17 | continue 18 | 19 | if not second.isdigit(): 20 | continue 21 | 22 | total += int(first) * int(second) 23 | 24 | return total 25 | -------------------------------------------------------------------------------- /ch05-files/e18b3_count_vowels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 18, beyond 3: count_vowels""" 3 | 4 | 5 | def count_vowels(filename): 6 | output = dict.fromkeys('aeiou', 0) 7 | 8 | for one_line in open(filename): 9 | for one_character in one_line.lower(): 10 | if one_character in output: 11 | output[one_character] += 1 12 | 13 | return output 14 | -------------------------------------------------------------------------------- /ch05-files/e19_passwd_to_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 19: passwd_to_dict""" 3 | 4 | 5 | def passwd_to_dict(filename): 6 | """Expects to get a string argument, the name of a file in passwd format. 7 | Returns a dictionary in which the keys are the usernames from the file, 8 | and the values are the user IDs from the file. The user IDs should be 9 | returned as integers. 10 | """ 11 | users = {} 12 | with open(filename) as passwd: 13 | for line in passwd: 14 | if not line.startswith(('#', '\n')): 15 | user_info = line.split(':') 16 | users[user_info[0]] = int(user_info[2]) 17 | return users 18 | -------------------------------------------------------------------------------- /ch05-files/e19b1_shells_users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 19, beyond 1: shells_users""" 3 | 4 | from collections import defaultdict 5 | 6 | 7 | def shells_users(filename): 8 | output = defaultdict(list) 9 | 10 | for one_line in open(filename): 11 | if one_line.startswith(('#', '\n')): 12 | continue 13 | username, *ignore, shell = one_line.strip().split(':') 14 | 15 | output[shell].append(username) 16 | 17 | return output 18 | -------------------------------------------------------------------------------- /ch05-files/e19b2_factors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 19, beyond 2: factors""" 3 | 4 | from collections import defaultdict 5 | 6 | 7 | def factors(): 8 | output = defaultdict(list) 9 | 10 | numbers = input("Enter numbers, separated by spaces: ").split() 11 | 12 | for one_number in numbers: 13 | if not one_number.isdigit(): 14 | print(f'Ignoring {one_number}') 15 | continue 16 | 17 | one_number = int(one_number) 18 | for i in range(1, one_number): 19 | if not one_number % i: 20 | output[one_number].append(i) 21 | 22 | output[one_number].append(one_number) 23 | 24 | return output 25 | -------------------------------------------------------------------------------- /ch05-files/e19b3_user_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 19, beyond 2: user_info""" 3 | 4 | 5 | def user_info(filename): 6 | output = {} 7 | 8 | for one_line in open(filename): 9 | if one_line.startswith(('#', '\n')): 10 | continue 11 | username, passwd, uid, *ignore, homedir, shell = one_line.strip().split(':') 12 | 13 | output[username] = {'uid': uid, 14 | 'homedir': homedir, 15 | 'shell': shell} 16 | 17 | return output 18 | -------------------------------------------------------------------------------- /ch05-files/e20_wc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 20: wc""" 3 | 4 | 5 | def wordcount(filename): 6 | """Accepts a filename as an argument. Prints the number of lines, 7 | characters, words (separated by whitespace) and different words 8 | (case sensitive) in the file.""" 9 | 10 | counts = {'characters': 0, 11 | 'words': 0, 12 | 'lines': 0} 13 | unique_words = set() 14 | 15 | for one_line in open(filename): 16 | counts['lines'] += 1 17 | counts['characters'] += len(one_line) 18 | counts['words'] += len(one_line.split()) 19 | 20 | unique_words.update(one_line.split()) 21 | 22 | counts['unique words'] = len(unique_words) 23 | for key, value in counts.items(): 24 | print(f'{key}: {value}') 25 | -------------------------------------------------------------------------------- /ch05-files/e20b1_count_certain_words.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 20, beyond 1: count_certain_words""" 3 | 4 | 5 | def count_certain_words(): 6 | s = input("Enter a filename, and then words you want to track: ").strip() 7 | 8 | if not s: 9 | return None 10 | 11 | filename, *words = s.split() 12 | 13 | counts = dict.fromkeys(words, 0) 14 | 15 | for one_line in open(filename): 16 | for one_word in one_line: 17 | if one_word in counts: 18 | counts[one_word] += 1 19 | 20 | return counts 21 | -------------------------------------------------------------------------------- /ch05-files/e20b2_file_sizes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 20, beyond 2: file_sizes""" 3 | 4 | import glob 5 | import os 6 | 7 | 8 | def file_sizes(dirname): 9 | counts = {one_filename: os.stat(one_filename).st_size 10 | for one_filename in glob.glob(f'{dirname}/*') 11 | if os.path.isfile(one_filename)} 12 | 13 | return counts 14 | -------------------------------------------------------------------------------- /ch05-files/e20b3_most_common_letters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 20, beyond 3: most_common_letters""" 3 | 4 | from collections import Counter 5 | import glob 6 | 7 | 8 | def most_common_letters(dirname): 9 | counts = Counter() 10 | 11 | for one_filename in glob.glob(f'{dirname}/*'): 12 | try: 13 | for one_line in open(one_filename): 14 | counts.update(one_line) 15 | except: 16 | pass 17 | 18 | return list(dict(counts.most_common(5)).keys()) 19 | -------------------------------------------------------------------------------- /ch05-files/e21_longest_word.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 21: longest_word""" 3 | 4 | 5 | import os 6 | 7 | 8 | def find_longest_word(filename): 9 | """Given a filename, return the longest word in the file.""" 10 | longest_word = '' 11 | for one_line in open(filename): 12 | for one_word in one_line.split(): 13 | if len(one_word) > len(longest_word): 14 | longest_word = one_word 15 | return longest_word 16 | 17 | 18 | def find_all_longest_words(dirname): 19 | """Given a directory name, return a dict in which the keys 20 | are filenames in the directory and the values are 21 | the strings -- the longest word in each file.""" 22 | return {filename: find_longest_word(os.path.join(dirname, filename)) 23 | for filename in os.listdir(dirname) 24 | if os.path.isfile(os.path.join(dirname, filename))} 25 | -------------------------------------------------------------------------------- /ch05-files/e21b1_md5_files.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 21, beyond 1: md5_files""" 3 | 4 | 5 | import glob 6 | import hashlib 7 | 8 | 9 | def md5_files(dirname): 10 | output = {} 11 | 12 | for one_filename in glob.glob(f'{dirname}/*'): 13 | try: 14 | m = hashlib.md5() 15 | m.update(one_filename.encode()) 16 | output[one_filename] = m.hexdigest() 17 | except: 18 | pass 19 | 20 | return output 21 | -------------------------------------------------------------------------------- /ch05-files/e21b2_mod_time.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 21, beyond 2: mod_times""" 3 | 4 | 5 | import glob 6 | import os 7 | 8 | 9 | def mod_times(dirname): 10 | output = {} 11 | 12 | for one_filename in glob.glob(f'{dirname}/*'): 13 | try: 14 | mod_time = os.stat(one_filename).ST_MTIME 15 | output[one_filename] = (arrow.now() - arrow.get(1503636889)).days 16 | 17 | except: 18 | pass 19 | 20 | return output 21 | -------------------------------------------------------------------------------- /ch05-files/e21b3_response_counts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 21, beyond 3: response_counts""" 3 | 4 | 5 | from collections import Counter 6 | 7 | 8 | def response_counts(filename): 9 | output = Counter() 10 | 11 | for one_line in open(filename): 12 | output[one_line.split()[8]] += 1 13 | 14 | return output 15 | -------------------------------------------------------------------------------- /ch05-files/e22_passwd_to_csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 22: passwd_to_csv""" 3 | 4 | 5 | import csv 6 | 7 | 8 | def passwd_to_csv(passwd_filename, csv_filename): 9 | """Function that takes the filename of a 10 | Unix-style passwd file to be read from, and the 11 | name of a file that will be created and written to. 12 | The username and user ID from the passwd file will 13 | be written to the second file in CSV format, with 14 | a tab separator. 15 | """ 16 | with open(passwd_filename) as passwd, open(csv_filename, 'w') as output: 17 | infile = csv.reader(passwd, delimiter=':') 18 | outfile = csv.writer(output, delimiter='\t') 19 | for record in infile: 20 | if len(record) > 1: 21 | outfile.writerow((record[0], record[2])) 22 | -------------------------------------------------------------------------------- /ch05-files/e22b1_passwd_to_csv_selected.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 22, beyond 1: passwd_to_csv_selected""" 3 | 4 | 5 | import csv 6 | 7 | 8 | def passwd_to_csv(passwd_filename, csv_filename, fields_to_pass='1 2', delimiter='\t'): 9 | fields_to_pass = [int(one_item) 10 | for one_item in fields_to_pass] 11 | 12 | with open(passwd_filename) as passwd, open(csv_filename, 'w') as output: 13 | infile = csv.reader(passwd, delimiter=':') 14 | outfile = csv.writer(output, delimiter=delimiter) 15 | for record in infile: 16 | 17 | if len(record) > 1: 18 | fields = [one_field 19 | for index, one_field in enumerate(record) 20 | if index in fields_to_pass] 21 | 22 | outfile.writerow(*fields) 23 | -------------------------------------------------------------------------------- /ch05-files/e22b2_dict_to_csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 22, beyond 2: dict_to_csv""" 3 | 4 | 5 | import csv 6 | 7 | 8 | def dict_to_csv(d, csv_filename): 9 | 10 | with open(csv_filename, 'w') as output: 11 | outfile = csv.writer(output, delimiter=delimiter) 12 | 13 | for key, value in d.items(): 14 | outfile.writerow([key, value, type(value)]) 15 | -------------------------------------------------------------------------------- /ch05-files/e22b3_random.csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 22, beyond 3: random_csv""" 3 | 4 | 5 | import random 6 | import csv 7 | 8 | 9 | def random_csv(csv_filename): 10 | 11 | with open(csv_filename, 'w') as output: 12 | outfile = csv.writer(output) 13 | 14 | for i in range(10): 15 | output = [] 16 | for j in range(10): 17 | output.append(random.randint(10, 100)) 18 | 19 | outfile.writerow(output) 20 | 21 | for one_line in open(csv_filename): 22 | numbers = [int(one_item) 23 | for one_item in one_line.split(',')] 24 | 25 | print(f'sum = {sum(numbers)}, mean = {sum(numbers)/len(numbers)}') 26 | -------------------------------------------------------------------------------- /ch05-files/e23_test_scores.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 23: test_scores""" 3 | 4 | import json 5 | import glob 6 | 7 | 8 | def print_scores(dirname): 9 | """Takes the name of a directory containing 10 | one or more JSON files with a ".json" suffix. 11 | The files contain test scores in a variety of 12 | subjects. 13 | 14 | For each class, the function prints the min, 15 | max, and average score for each subject. 16 | """ 17 | 18 | scores = {} 19 | 20 | for filename in glob.glob(f'{dirname}/*.json'): 21 | scores[filename] = {} 22 | 23 | with open(filename) as infile: 24 | for result in json.load(infile): 25 | for subject, score in result.items(): 26 | scores[filename].setdefault(subject, []) 27 | scores[filename][subject].append(score) 28 | 29 | for one_class in scores: 30 | print(one_class) 31 | for subject, subject_scores in scores[one_class].items(): 32 | min_score = min(subject_scores) 33 | max_score = max(subject_scores) 34 | average_score = (sum(subject_scores) / 35 | len(subject_scores)) 36 | 37 | print(subject) 38 | print(f'\tmin {min_score}') 39 | print(f'\tmax {max_score}') 40 | print(f'\taverage {average_score}') 41 | -------------------------------------------------------------------------------- /ch05-files/e23b1_json_passwd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 23, beyond 1: json_passwd""" 3 | 4 | 5 | import json 6 | 7 | 8 | def json_passwd(filename): 9 | output = [] 10 | for one_line in open(filename): 11 | if one_line.startswith('#'): 12 | continue 13 | if one_line.strip().startswith('\n'): 14 | continue 15 | 16 | output.append(tuple(one_line.split(':'))) 17 | 18 | return json.dumps(output) 19 | -------------------------------------------------------------------------------- /ch05-files/e23b2_json_passwd_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 23, beyond 2: json_passwd_dict""" 3 | 4 | 5 | import json 6 | 7 | 8 | def json_passwd_dict(filename): 9 | fields = ['username', 'password', 'uid', 'gid', 'name', 'homedir', 'shell'] 10 | 11 | output = [] 12 | for one_line in open(filename): 13 | if one_line.startswith('#'): 14 | continue 15 | if one_line.strip().startswith('\n'): 16 | continue 17 | 18 | output.append(dict(zip(fields, one_line.split(':')))) 19 | 20 | return json.dumps(output) 21 | -------------------------------------------------------------------------------- /ch05-files/e23b3_file_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 23, beyond 3: file_info""" 3 | 4 | 5 | import os 6 | import glob 7 | import json 8 | 9 | 10 | def write_file_info(dirname, outputfile): 11 | output = [] 12 | for one_filename in glob.glob(f'{dirname}/*'): 13 | if not os.path.isfile(one_filename): 14 | continue 15 | 16 | try: 17 | output.append({'filename': one_filename, 18 | 'size': os.stat(one_filename).st_size, 19 | 'mtime': os.stat(one_filename).st_mtime}) 20 | except: 21 | print(f'Error reading {filename}; skipping') 22 | 23 | return json.dump(output, open(outputfile, 'w')) 24 | 25 | 26 | def read_file_info(filename): 27 | output = {} 28 | 29 | file_info = json.load(filename) 30 | 31 | return output 32 | -------------------------------------------------------------------------------- /ch05-files/e24_reverse_lines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 24: reverse_lines""" 3 | 4 | 5 | def reverse_lines(infilename, outfilename): 6 | """Takes two filenames as arguments. The 7 | first is for reading, and the second is for writing. 8 | The contents of the first file are written to 9 | the second, but in reverse order. 10 | """ 11 | with open(infilename) as infile, open(outfilename, 'w') as outfile: 12 | for one_line in infile: 13 | outfile.write(f'{one_line.rstrip()[::-1]}\n') 14 | -------------------------------------------------------------------------------- /ch05-files/e24b1_encrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 24, beyond 1: encrypt""" 3 | 4 | 5 | def encrypt(filename, text): 6 | with open(filename, 'w') as outfile: 7 | for one_character in text: 8 | outfile.write(f'{ord(one_character)}\n') 9 | 10 | 11 | def decrypt(filename): 12 | characters = [chr(int(one_character)) 13 | for one_character in open(filename) 14 | if one_character.strip().isdigit()] 15 | return ''.join(characters) 16 | -------------------------------------------------------------------------------- /ch05-files/e24b2_vowels_and_consonants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 24, beyond 2: vowels_and_consonants""" 3 | 4 | import string 5 | 6 | 7 | def vowels_and_consonants(infilename, vowel_filename, consonant_filename): 8 | with open(infilename) as infile, open(vowel_filename, 'w') as vowel_out, open(consonant_filename, 'w') as consonant_out: 9 | for one_line in infile: 10 | for one_character in one_line: 11 | if one_character.lower() in 'aeiou': 12 | vowel_out.write(one_character) 13 | elif one_character.lower() in string.ascii_lowercase: 14 | consonant_out.write(one_character) 15 | -------------------------------------------------------------------------------- /ch05-files/e24b3_shell_users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 5, exercise 24, beyond 3: shell_users""" 3 | 4 | from collections import defaultdict 5 | 6 | 7 | def shell_users(filename, outfilename): 8 | shells = defaultdict(list) 9 | 10 | with open(filename) as passwd, open(outfilename, 'w') as outfile: 11 | for one_line in passwd: 12 | if one_line.startswith(('#', '\n')): 13 | continue 14 | 15 | username, *fields, shell = one_line.strip().split(':') 16 | shells[shell].append(username) 17 | 18 | for shell, all_users in shells.items(): 19 | outfile.write(f'{shell}\t{",".join(all_users)}\n') 20 | -------------------------------------------------------------------------------- /ch05-files/json-files.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reuven/python-workout/b9c68520d572bf70eff8e554a8ee9c8702c88e6e/ch05-files/json-files.zip -------------------------------------------------------------------------------- /ch05-files/myoutput.txt: -------------------------------------------------------------------------------- 1 | 104 2 | 101 3 | 108 4 | 108 5 | 111 6 | 10 7 | 111 8 | 117 9 | 116 10 | 10 11 | 116 12 | 104 13 | 101 14 | 114 15 | 101 16 | 33 17 | 33 18 | 33 19 | 10 20 | -------------------------------------------------------------------------------- /ch05-files/myshells.txt: -------------------------------------------------------------------------------- 1 | /usr/bin/false nobody,daemon,_taskgated,_networkd,_installassistant,_lp,_postfix,_scsd,_ces,_appstore,_mcxalr,_appleevents,_geod,_devdocs,_sandbox,_mdnsresponder,_ard,_www,_eppc,_cvs,_svn,_mysql,_sshd,_qtss,_cyrus,_mailman,_appserver,_clamav,_amavisd,_jabber,_appowner,_windowserver,_spotlight,_tokend,_securityagent,_calendar,_teamsserver,_update_sharing,_installer,_atsserver,_ftp,_unknown,_softwareupdate,_coreaudiod,_screensaver,_locationd,_trustevaluationagent,_timezone,_lda,_cvmsroot,_usbmuxd,_dovecot,_dpaudio,_postgres,_krbtgt,_kadmin_admin,_kadmin_changepw,_devicemgr,_webauthserver,_netbios,_warmd,_dovenull,_netstatistics,_avbdeviced,_krb_krbtgt,_krb_kadmin,_krb_changepw,_krb_kerberos,_krb_anonymous,_assetcache,_coremediaiod,_launchservicesd,_iconservices,_distnote,_nsurlsessiond,_nsurlstoraged,_displaypolicyd,_astris,_krbfast,_gamecontrollerd,_ondemand,_xserverdocs,_wwwproxy,_mobileasset,_findmydevice,_datadetectors,_captiveagent,_ctkd,_applepay,_hidd,_cmiodalassistants,_analyticsd,_fpsd,_timed,_nearbyd,_reportmemoryexception,_driverkit 2 | /bin/sh root 3 | /usr/sbin/uucico _uucp 4 | /bin/bash _mbsetupuser 5 | -------------------------------------------------------------------------------- /ch05-files/output.csv: -------------------------------------------------------------------------------- 1 | 21,47,8,40,87,52,3,16,94,32 2 | 43,39,41,28,45,71,5,23,13,23 3 | 48,2,52,68,31,15,1,86,19,74 4 | 6,51,78,55,34,39,31,40,70,22 5 | 90,29,52,50,25,34,99,32,70,54 6 | 27,42,22,61,72,88,51,8,86,70 7 | 54,92,32,98,96,74,69,70,65,94 8 | 5,97,43,9,44,89,1,47,42,72 9 | 82,49,71,78,10,50,75,57,74,55 10 | 67,48,36,98,10,76,39,48,86,3 11 | -------------------------------------------------------------------------------- /ch05-files/passwd-consonants: -------------------------------------------------------------------------------- 1 | srDtbsNtthtthsflscnsltddrctlynlywhnthsystmsrnnngnsnglsrmdtthrtmsthsnfrmtnsprvddbypnDrctrySthpndrctrydmnpgfrddtnlnfrmtnbtpnDrctrynbdynprvlgdsrvrmptysrbnflsrtSystmdmnstrtrvrrtbnshdmnSystmSrvcsvrrtsrbnflscpnxtnxCpyPrtclvrsplcpsrsbncctskgtdTskGtDmnvrmptysrbnflsntwrkdNtwrkSrvcsvrntwrkdsrbnflsnstllssstntnstllssstntvrmptysrbnflslpPrntngSrvcsvrsplcpssrbnflspstfxPstfxMlSrvrvrsplpstfxsrbnflsscsdSrvcCnfgrtnSrvcvrmptysrbnflscsCrtfctnrllmntSrvcvrmptysrbnflsppstrMcppStrSrvcvrdbppstrsrbnflsmcxlrMCXppLnchvrmptysrbnflspplvntspplvntsDmnvrmptysrbnflsgdGSrvcsDmnvrdbgdsrbnflsdvdcsDvlprDcmnttnvrmptysrbnflssndbxStbltvrmptysrbnflsmdnsrspndrmDNSRspndrvrmptysrbnflsrdpplRmtDsktpvrmptysrbnflswwwWrldWdWbSrvrLbrryWbSrvrsrbnflsppcpplvntssrvrmptysrbnflscvsCVSSrvrvrmptysrbnflssvnSVNSrvrvrmptysrbnflsmysqlMySQLSrvrvrmptysrbnflssshdsshdPrvlgsprtnvrmptysrbnflsqtssQckTmStrmngSrvrvrmptysrbnflscyrsCyrsdmnstrtrvrmpsrbnflsmlmnMlmnLstSrvrvrmptysrbnflsppsrvrpplctnSrvrvrmptysrbnflsclmvClmVDmnvrvrsmlssrbnflsmvsdMVSDmnvrvrsmlssrbnflsjbbrJbbrXMPPSrvrvrmptysrbnflsppwnrpplctnwnrvrmptysrbnflswndwsrvrWndwSrvrvrmptysrbnflssptlghtSptlghtvrmptysrbnflstkndTknDmnvrmptysrbnflsscrtygntScrtygntvrdbscrtygntsrbnflsclndrClndrvrmptysrbnflstmssrvrTmsSrvrvrtmssrvrsrbnflspdtshrngpdtShrngvrmptysrbnflsnstllrnstllrvrmptysrbnflstssrvrTSSrvrvrmptysrbnflsftpFTPDmnvrmptysrbnflsnknwnnknwnsrvrmptysrbnflssftwrpdtSftwrpdtSrvcvrdbsftwrpdtsrbnflscrddCrdDmnvrmptysrbnflsscrnsvrScrnsvrvrmptysrbnflslctndLctnDmnvrdblctndsrbnflstrstvltngntTrstvltngntvrmptysrbnflstmzntTmZnDmnvrmptysrbnflsldLclDlvrygntvrmptysrbnflscvmsrtCVMSRtvrmptysrbnflssbmxdPhnSDvcHlprvrdblckdwnsrbnflsdvctDvctdmnstrtrvrmptysrbnflsdpdDPdvrmptysrbnflspstgrsPstgrSQLSrvrvrmptysrbnflskrbtgtKrbrsTcktGrntngTcktvrmptysrbnflskdmndmnKrbrsdmnSrvcvrmptysrbnflskdmnchngpwKrbrsChngPsswrdSrvcvrmptysrbnflsdvcmgrDvcMngmntSrvrvrmptysrbnflswbthsrvrWbthSrvrvrmptysrbnflsntbsNtBSvrmptysrbnflswrmdWrmDmnvrmptysrbnflsdvnllDvctthntctnvrmptysrbnflsntsttstcsNtwrkSttstcsDmnvrmptysrbnflsvbdvcdthrntVBDvcDmnvrmptysrbnflskrbkrbtgtpnDrctryKrbrsTcktGrntngTcktvrmptysrbnflskrbkdmnpnDrctryKrbrsdmnSrvcvrmptysrbnflskrbchngpwpnDrctryKrbrsChngPsswrdSrvcvrmptysrbnflskrbkrbrspnDrctryKrbrsvrmptysrbnflskrbnnymspnDrctryKrbrsnnymsvrmptysrbnflssstcchsstCchSrvcvrmptysrbnflscrmddCrMdDmnvrmptysrbnflslnchsrvcsdlnchsrvcsdvrmptysrbnflscnsrvcscnSrvcsvrmptysrbnflsdstntDstNtvrmptysrbnflsnsrlsssndNSRLSssnDmnvrdbnsrlsssndsrbnflsnsrlstrgdNSRLStrgDmnvrdbnsrlstrgdsrbnflsdsplyplcydDsplyPlcyDmnvrmptysrbnflsstrsstrsSrvcsvrdbstrssrbnflskrbfstKrbrsFSTccntvrmptysrbnflsgmcntrllrdGmCntrllrDmnvrmptysrbnflsmbstpsrStpsrvrstpbnbshndmndnDmndRsrcDmnvrdbndmndsrbnflsxsrvrdcsmcSSrvrDcmntsSrvcvrmptysrbnflswwwprxyWWWPrxyvrmptysrbnflsmblsstMblsstsrvrmsrbnflsfndmydvcFndMyDvcDmnvrdbfndmydvcsrbnflsdtdtctrsDtDtctrsvrdbdtdtctrssrbnflscptvgntcptvgntvrmptysrbnflsctkdctkdccntvrmptysrbnflspplpypplpyccntvrdbpplpysrbnflshddHDSrvcsrvrdbhddsrbnflscmdlssstntsCrMdssstntssrvrdbcmdlssstntssrbnflsnlytcsdnlytcsDmnvrdbnlytcsdsrbnflsfpsdFPSDmnvrdbfpsdsrbnflstmdTmSyncDmnvrdbtmdsrbnflsnrbydPrxmtyndRngngDmnvrdbnrbydsrbnflsrprtmmryxcptnRprtMmryxcptnvrdbrprtmmryxcptnsrbnflsdrvrktDrvrKtvrmptysrbnfls -------------------------------------------------------------------------------- /ch05-files/passwd-vowels: -------------------------------------------------------------------------------- 1 | UeaaaeoeaiieioueieoeeeiuiiieueoeAoeieiioaioioieOeieoeeeoeieoaaeoaiioaioaioaouOeieoooUiieeUeaeuiaeooeAiiaoaooiaeoeeieaoouiaeuuUioUiooooaoouuuiuuioaaeaaeaeoaeuiaeeoeoeieaeouiaeiaaiaIaAiaaeuiaeiieieaoouuiaeoioiaieeaoooiuiaeeieoiuaioeieaeuiaeeeiiaeEoeeieaeuiaeaoeaAoeeieaaoeuiaeaAauaeuiaeaeeeAeEeaeoaeuiaeeoeoeieaeoaeouiaeeoeeoeoueaioaeuiaeaoeaeaeuiaeeoeeoeaeuiaeaAeeoeeoaeuiaeoieeeeiaeeeuiaeeAeEeUeaeuiaeeeaeuiaeeeaeuiaeeeaeuiaeiieeeaaioaeuiaeuiieeaieeaeuiaeuuAiiaoaiauiaeaiaaiaieeaeuiaeaeeAiaioeeaeuiaeaaaAaeoaiuaiuiaeaaiAaiaeoaiuaiuiaeaeaeeeaeuiaeaoeAiaioOeaeuiaeioeeioeeaeuiaeoioiaeuiaeoeoeaeoaeuiaeeuiaeeuiAeaeuiaeuiaeaeaaeaaeuiaeeaeeeaeeaeaeeuiaeuaeaiUaeaiaeuiaeiaeIaeaeuiaeaeeAeeaeuiaeaeoaeuiaeuoUoUeaeuiaeoaeuaeoaeUaeeieaoaeuaeuiaeoeauiooeAuioaeoaeuiaeeeaeeeaeaeuiaeoaiooaioaeoaoaiouiaeueauaioaeuEauaioAeaeuiaeieoeAuoieoeaeoaeuiaeaoaeieAeaeuiaeooooaeuiaeuuioeOeieeeaoouiaeoeooeoAiiaoaeuiaeauioAuioaeuiaeoeoeeeaeuiaeeeoieaiieaeuiaeaiaieeoAieieaeuiaeaiaeeeoaeaoeieaeuiaeeieeieaaeeeeaeuiaeeaueeeAueeaeuiaeeioeIOaeuiaeaaaeoaeuiaeoeuoeoAueiaioaeuiaeeaiieoaiiaeoaeuiaeaeieEeeAeieaeoaeuiaeOeieoeeoieaiieaeuiaeaiOeieoeeoAieieaeuiaeaeOeieoeeoaeaoeieaeuiaeeeoOeieoeeoaeuiaeaoouOeieoeeoAoouaeuiaeaeaeAeaeeieaeuiaeoeeiaiooeeiaIOaeoaeuiaeaueieaueieaeuiaeioeieIoeieaeuiaeioeioeaeuiaeueioUeioaeoaueiouiaeuoaeUoaeaeoauoaeuiaeiaoiiaoiaeoaeuiaeaiAieieaaiuiaeaeeoAAouaeuiaeaeooeaeooeaeoaeuiaeeuueeuUeaeuiaoeaOeaeoueaeoaoeauiaeeeoaOeeoueeieaeuiaeooaeuiaeoieaeoieAeUeaauiaeieieieieaeoaieieuiaeaaeeoaaeeoaaaeeouiaeaieaeaieaeaeuiaeAouaeuiaeaeaaeaAouaaeauiaeiIeieUeaiuiaeioaaiaoeeiaIOAiaUeaioaaiauiaeaaiAaiaeoaaaiuiaeaeoauiaeieieaeoaieuiaeeaoiiaaiaeoaeauiaeeoeoeeioeoeoEeioaeoeoeeiouiaeieiieiaeuiae -------------------------------------------------------------------------------- /ch05-files/passwd.csv: -------------------------------------------------------------------------------- 1 | root 0 2 | daemon 1 3 | bin 2 4 | sys 3 5 | sync 4 6 | games 5 7 | man 6 8 | lp 7 9 | mail 8 10 | news 9 11 | uucp 10 12 | proxy 13 13 | www-data 33 14 | backup 34 15 | list 38 16 | irc 39 17 | gnats 41 18 | nobody 65534 19 | syslog 101 20 | messagebus 102 21 | landscape 103 22 | jci 955 23 | sshd 104 24 | user 1000 25 | reuven 1001 26 | postfix 105 27 | colord 106 28 | postgres 107 29 | dovecot 108 30 | dovenull 109 31 | postgrey 110 32 | debian-spamd 111 33 | memcache 113 34 | genadi 1002 35 | shira 1003 36 | atara 1004 37 | shikma 1005 38 | amotz 1006 39 | mysql 114 40 | clamav 115 41 | amavis 116 42 | opendkim 117 43 | gitlab-redis 999 44 | gitlab-psql 998 45 | git 1007 46 | opendmarc 118 47 | dkim-milter-python 119 48 | deploy 1008 49 | redis 112 50 | -------------------------------------------------------------------------------- /ch05-files/scores/9a.json: -------------------------------------------------------------------------------- 1 | [{"math" : 90, "literature" : 98, "science" : 97}, 2 | {"math" : 65, "literature" : 79, "science" : 85}, 3 | {"math" : 78, "literature" : 83, "science" : 75}, 4 | {"math" : 92, "literature" : 78, "science" : 85}, 5 | {"math" : 100, "literature" : 80, "science" : 90} 6 | ] 7 | -------------------------------------------------------------------------------- /ch05-files/scores/9b.json: -------------------------------------------------------------------------------- 1 | [{"math" : 95, "literature" : 92, "science" : 93}, 2 | {"math" : 80, "literature" : 85, "science" : 79}, 3 | {"math" : 68, "literature" : 90, "science" : 90}, 4 | {"math" : 100, "literature" : 78, "science" : 100}, 5 | {"math" : 85, "literature" : 75, "science" : 90} 6 | ] 7 | -------------------------------------------------------------------------------- /ch05-files/scores/json-files.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reuven/python-workout/b9c68520d572bf70eff8e554a8ee9c8702c88e6e/ch05-files/scores/json-files.zip -------------------------------------------------------------------------------- /ch05-files/scores/json-files/9a.json: -------------------------------------------------------------------------------- 1 | [{"math" : 90, "literature" : 98, "science" : 97}, 2 | {"math" : 65, "literature" : 79, "science" : 85}, 3 | {"math" : 78, "literature" : 83, "science" : 75}, 4 | {"math" : 92, "literature" : 78, "science" : 85}, 5 | {"math" : 100, "literature" : 80, "science" : 90} 6 | ] 7 | -------------------------------------------------------------------------------- /ch05-files/scores/json-files/9b.json: -------------------------------------------------------------------------------- 1 | [{"math" : 95, "literature" : 92, "science" : 93}, 2 | {"math" : 80, "literature" : 85, "science" : 79}, 3 | {"math" : 68, "literature" : 90, "science" : 90}, 4 | {"math" : 100, "literature" : 78, "science" : 100}, 5 | {"math" : 85, "literature" : 75, "science" : 90} 6 | ] 7 | -------------------------------------------------------------------------------- /ch05-files/test_e18_final_line.py: -------------------------------------------------------------------------------- 1 | from e18_final_line import get_final_line 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def empty_file(tmp_path): 7 | f = tmp_path / 'filename.txt' 8 | f.write_text('') 9 | return f 10 | 11 | 12 | @pytest.fixture 13 | def simple_file(tmp_path): 14 | f = tmp_path / 'filename.txt' 15 | f.write_text('abcd\nefgh\nijkl\n') 16 | return f 17 | 18 | 19 | def test_empty(empty_file): 20 | assert get_final_line(empty_file) == '' 21 | 22 | 23 | def test_simple(simple_file): 24 | assert get_final_line(simple_file) == 'ijkl\n' 25 | -------------------------------------------------------------------------------- /ch05-files/test_e19_passwd_to_dict.py: -------------------------------------------------------------------------------- 1 | from e19_passwd_to_dict import passwd_to_dict 2 | import pytest 3 | from io import StringIO 4 | 5 | 6 | @pytest.fixture 7 | def empty_passwd(tmp_path): 8 | f = tmp_path / 'passwd' 9 | f.write_text('') 10 | return f 11 | 12 | 13 | @pytest.fixture 14 | def simple_passwd(tmp_path): 15 | f = tmp_path / 'passwd' 16 | f.write_text('''root:x:0:0:root:/root:/bin/bash 17 | daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin 18 | bin:x:2:2:bin:/bin:/usr/sbin/nologin 19 | sys:x:3:3:sys:/dev:/usr/sbin/nologin 20 | sync:x:4:65534:sync:/bin:/bin/sync 21 | games:x:5:60:games:/usr/games:/usr/sbin/nologin 22 | man:x:6:12:man:/var/cache/man:/usr/sbin/nologin 23 | lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin 24 | mail:x:8:8:mail:/var/mail:/usr/sbin/nologin 25 | ''') 26 | return f 27 | 28 | 29 | @pytest.fixture 30 | def complex_passwd(tmp_path): 31 | f = tmp_path / 'passwd' 32 | f.write_text('''root:x:0:0:root:/root:/bin/bash 33 | daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin 34 | bin:x:2:2:bin:/bin:/usr/sbin/nologin 35 | sys:x:3:3:sys:/dev:/usr/sbin/nologin 36 | # this is a comment line 37 | # and then we have some blank lines 38 | 39 | sync:x:4:65534:sync:/bin:/bin/sync 40 | games:x:5:60:games:/usr/games:/usr/sbin/nologin 41 | man:x:6:12:man:/var/cache/man:/usr/sbin/nologin 42 | lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin 43 | mail:x:8:8:mail:/var/mail:/usr/sbin/nologin 44 | ''') 45 | return f 46 | 47 | 48 | def test_empty(empty_passwd): 49 | assert passwd_to_dict(empty_passwd) == {} 50 | 51 | 52 | def test_simple(simple_passwd): 53 | d = passwd_to_dict(simple_passwd) 54 | assert len(d) == 9 55 | assert d['root'] == 0 56 | assert d['sys'] == 3 57 | assert d['mail'] == 8 58 | 59 | 60 | def test_complex(complex_passwd): 61 | d = passwd_to_dict(complex_passwd) 62 | assert len(d) == 9 63 | assert d['root'] == 0 64 | assert d['sys'] == 3 65 | assert d['mail'] == 8 66 | -------------------------------------------------------------------------------- /ch05-files/test_e20_wc.py: -------------------------------------------------------------------------------- 1 | from e20_wc import wordcount 2 | import pytest 3 | from io import StringIO 4 | 5 | 6 | @pytest.fixture 7 | def empty_file(tmp_path): 8 | f = tmp_path / 'textfile' 9 | f.write_text('') 10 | return f 11 | 12 | 13 | @pytest.fixture 14 | def simple_file(tmp_path): 15 | f = tmp_path / 'wcfile.txt' 16 | f.write_text('''This is a test file. 17 | 18 | It contains 28 words and 20 different words. 19 | 20 | It also contains 165 characters. 21 | 22 | It also contains 11 lines. 23 | 24 | It is also self-referential. 25 | 26 | Wow!''') 27 | return f 28 | 29 | 30 | def test_empty(empty_file, capsys): 31 | wordcount(empty_file) 32 | captured_out, captured_err = capsys.readouterr() 33 | assert captured_out == """characters: 0 34 | words: 0 35 | lines: 0 36 | unique words: 0 37 | """ 38 | 39 | 40 | def test_simple(simple_file, capsys): 41 | wordcount(simple_file) 42 | captured_out, captured_err = capsys.readouterr() 43 | assert captured_out == """characters: 164 44 | words: 28 45 | lines: 11 46 | unique words: 20 47 | """ 48 | -------------------------------------------------------------------------------- /ch05-files/test_e21_longest_word.py: -------------------------------------------------------------------------------- 1 | from e21_longest_word import find_longest_word, find_all_longest_words 2 | import pytest 3 | from io import StringIO 4 | 5 | 6 | @pytest.fixture 7 | def empty_file(tmp_path): 8 | f = tmp_path / 'emptyfile.txt' 9 | f.write_text('') 10 | return f 11 | 12 | 13 | @pytest.fixture 14 | def small_file(tmp_path): 15 | f = tmp_path / 'smallfile.txt' 16 | f.write_text('''This is the first line 17 | and this is the second line''') 18 | return f 19 | 20 | 21 | @pytest.fixture 22 | def big_file(tmp_path): 23 | f = tmp_path / 'bigfile.txt' 24 | f.write_text('''This is the first line of a big file 25 | 26 | and this is the second line 27 | and this is, to no one's surprise, the third line 28 | but the biggest word will probably be encyclopedia''') 29 | return f 30 | 31 | 32 | def test_small_file(small_file): 33 | assert find_longest_word(small_file) == 'second' 34 | 35 | 36 | def test_big_file(big_file): 37 | assert find_longest_word(big_file) == 'encyclopedia' 38 | 39 | 40 | def test_empty_directory(tmp_path): 41 | assert find_all_longest_words(tmp_path) == {} 42 | 43 | 44 | def test_one_file(tmp_path, empty_file): 45 | assert find_all_longest_words(tmp_path) == {'emptyfile.txt': ''} 46 | 47 | 48 | def test_all_files(tmp_path, empty_file, small_file, big_file): 49 | assert find_all_longest_words(tmp_path) == {'emptyfile.txt': '', 50 | 'smallfile.txt': 'second', 51 | 'bigfile.txt': 'encyclopedia'} 52 | -------------------------------------------------------------------------------- /ch05-files/test_e22_passwd_to_csv.py: -------------------------------------------------------------------------------- 1 | from e22_passwd_to_csv import passwd_to_csv 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def passwd_file(tmp_path): 7 | f = tmp_path / 'passwd' 8 | f.write_text(''' 9 | root:x:0:0:root:/root:/bin/bash 10 | daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin 11 | bin:x:2:2:bin:/bin:/usr/sbin/nologin 12 | sys:x:3:3:sys:/dev:/usr/sbin/nologin 13 | sync:x:4:65534:sync:/bin:/bin/sync 14 | games:x:5:60:games:/usr/games:/usr/sbin/nologin 15 | man:x:6:12:man:/var/cache/man:/usr/sbin/nologin 16 | lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin 17 | mail:x:8:8:mail:/var/mail:/usr/sbin/nologin 18 | atara:x:1004:1005:Atara Lerner-Friedman,,,:/home/atara:/bin/bash 19 | shikma:x:1005:1006:Shikma Lerner-Friedman,,,:/home/shikma:/bin/bash 20 | amotz:x:1006:1007:Amotz Lerner-Friedman,,,:/home/amotz:/bin/bash 21 | ''') 22 | return f 23 | 24 | 25 | def test_passwd_to_csv(passwd_file): 26 | passwd_to_csv(passwd_file, 'output.csv') 27 | 28 | csv_content = open('output.csv').read() 29 | assert len(csv_content) == 95 30 | assert csv_content.splitlines()[0] == 'root\t0' 31 | assert csv_content.splitlines()[-1] == 'amotz\t1006' 32 | -------------------------------------------------------------------------------- /ch05-files/test_e23_test_scores.py: -------------------------------------------------------------------------------- 1 | from e23_test_scores import print_scores 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def score_file_1(tmp_path): 7 | j1 = tmp_path / '9a.json' 8 | j1.write_text(''' 9 | [{"math" : 90, "literature" : 98, "science" : 97}, 10 | {"math" : 65, "literature" : 79, "science" : 85}, 11 | {"math" : 78, "literature" : 83, "science" : 75}, 12 | {"math" : 92, "literature" : 78, "science" : 85}, 13 | {"math" : 100, "literature" : 80, "science" : 90} 14 | ] 15 | ''') 16 | return j1 17 | 18 | 19 | @pytest.fixture 20 | def score_file_2(tmp_path): 21 | j2 = tmp_path / '9b.json' 22 | j2.write_text(''' 23 | [{"math" : 70, "literature" : 98, "science" : 97}, 24 | {"math" : 65, "literature" : 83, "science" : 70}, 25 | {"math" : 58, "literature" : 83, "science" : 75}, 26 | {"math" : 72, "literature" : 78, "science" : 85}, 27 | {"math" : 100, "literature" : 80, "science" : 90} 28 | ] 29 | ''') 30 | return j2 31 | 32 | 33 | def test_scores(tmp_path, score_file_1, score_file_2, capsys): 34 | print_scores(tmp_path) 35 | captured_out, captured_err = capsys.readouterr() 36 | assert '9a.json' in captured_out 37 | assert '9b.json' in captured_out 38 | # assert captured_out.count(' min ') == 6 39 | # assert captured_out.count(' max ') == 6 40 | # assert captured_out.count(' average ') == 6 41 | -------------------------------------------------------------------------------- /ch05-files/test_e24_reverse_lines.py: -------------------------------------------------------------------------------- 1 | from e24_reverse_lines import reverse_lines 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def big_file(tmp_path): 7 | f = tmp_path / 'bigfile.txt' 8 | f.write_text('''This is the first line of a big file 9 | 10 | and this is the second line 11 | and this is, to no one's surprise, the third line 12 | but the biggest word will probably be encyclopedia''') 13 | return f 14 | 15 | 16 | def test_reversing_lines(big_file): 17 | reverse_lines(big_file, 'output.txt') 18 | content = open('output.txt').read() 19 | assert len(content) == 167 20 | assert content[:18] == 'elif gib a fo enil' 21 | assert content[-18:] == 'w tseggib eht tub\n' 22 | -------------------------------------------------------------------------------- /ch05-files/test_file.txt: -------------------------------------------------------------------------------- 1 | 10 5 2 | 20 10 3 | 30 15 4 | 40 20 5 | -------------------------------------------------------------------------------- /ch06-functions/.ipynb_checkpoints/Untitled-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 4 6 | } 7 | -------------------------------------------------------------------------------- /ch06-functions/e25_xml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 6, exercise 25: myxml""" 4 | 5 | 6 | def myxml(tagname, content='', **kwargs): 7 | """Takes a tag name (string), an optional content string, 8 | and optional kwargs. 9 | 10 | Returns a string in which "tagname" is an XML tag at the start and end, 11 | "content" is placed in the middle of the tags, and 12 | the key-value pairs of kwargs are inserted as attributes in the opening tag. 13 | """ 14 | attrs = ''.join([f' {key}="{value}"' 15 | for key, value in kwargs.items()]) 16 | return f'<{tagname}{attrs}>{content}' 17 | -------------------------------------------------------------------------------- /ch06-functions/e25b1_copyfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 6, exercise 25, beyond 1: copyfile""" 3 | 4 | 5 | def copyfile(infilename, *args): 6 | for outfilename in args: 7 | with open(outfilename, 'w') as outfile: 8 | for one_line in open(infilename): 9 | outfile.write(one_line) 10 | -------------------------------------------------------------------------------- /ch06-functions/e25b2_factorial.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 6, exercise 25, beyond 2: factorialish""" 3 | 4 | 5 | def factorialish(*args): 6 | if not args: 7 | return 0 8 | 9 | total = args[0] 10 | for one_number in args[1:]: 11 | total *= one_number 12 | 13 | return total 14 | -------------------------------------------------------------------------------- /ch06-functions/e25b3_anyjoin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Solution to chapter 6, exercise 25, beyond 3: anyjoin""" 3 | 4 | 5 | def anyjoin(seq, glue=' '): 6 | return glue.join([str(one_item) 7 | for one_item in seq]) 8 | -------------------------------------------------------------------------------- /ch06-functions/e26_calc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 6, exercise 26: calc""" 4 | 5 | import operator 6 | 7 | 8 | def calc(to_solve): 9 | """This function expects to get a string containing a 10 | two-argument math expression in prefix notation, and with 11 | whitespace separating the operator and numbers. 12 | The return value is the result from invoking this operation. 13 | """ 14 | 15 | operations = {'+': operator.add, 16 | '-': operator.sub, 17 | '*': operator.mul, 18 | '/': operator.truediv, 19 | '**': operator.pow, 20 | '%': operator.mod} 21 | 22 | op, first_s, second_s = to_solve.split() 23 | first = int(first_s) 24 | second = int(second_s) 25 | 26 | return operations[op](first, second) 27 | -------------------------------------------------------------------------------- /ch06-functions/e26b1_calc_args.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 6, exercise 26, beyond 1: calc_args""" 4 | 5 | import operator 6 | 7 | 8 | def calc_args(to_solve): 9 | 10 | operations = {'+': operator.add, 11 | '-': operator.sub, 12 | '*': operator.mul, 13 | '/': operator.truediv, 14 | '**': operator.pow, 15 | '%': operator.mod} 16 | 17 | op, *numbers = to_solve.split() 18 | 19 | if not numbers: 20 | return 0 21 | 22 | output = int(numbers[0]) 23 | for one_number in numbers[1:]: 24 | output = operations[op](output, int(one_number)) 25 | 26 | return output 27 | -------------------------------------------------------------------------------- /ch06-functions/e26b2_apply_to_each.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 6, exercise 26, beyond 2: apply_to_each""" 4 | 5 | 6 | def apply_to_each(f, seq): 7 | 8 | return [f(one_item) 9 | for one_item in seq] 10 | -------------------------------------------------------------------------------- /ch06-functions/e26b3_transform_lines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 6, exercise 26, beyond 3: transform_lines""" 4 | 5 | 6 | def transform_lines(f, infilename, outfilename): 7 | with open(infilename) as infile, open(outfilename, 'w') as outfile: 8 | for one_line in infile: 9 | outfile.write(f(one_line)) 10 | -------------------------------------------------------------------------------- /ch06-functions/e27_makepw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 6, exercise 27: makepw""" 4 | 5 | import random 6 | 7 | 8 | def create_password_generator(characters): 9 | """This function takes a string as input. 10 | 11 | It returns a function that, when invoked with an 12 | integer argument, returns a string containing 13 | a random selection from "characters", of length 14 | "length". 15 | """ 16 | def create_password(length): 17 | output = [] 18 | 19 | for i in range(length): 20 | output.append(random.choice(characters)) 21 | return ''.join(output) 22 | return create_password 23 | -------------------------------------------------------------------------------- /ch06-functions/e27b1_password_checker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 6, exercise 27, beyond 1: password_checker""" 4 | 5 | import string 6 | 7 | 8 | def create_password_checker(min_uppercase, min_lowercase, min_punctuation, min_digits): 9 | uppercase_set = set(string.ascii_uppercase) 10 | lowercase_set = set(string.ascii_lowercase) 11 | punctuation_set = set(string.punctuation) 12 | digits_set = set(string.digits) 13 | 14 | def check_password(password): 15 | 16 | if len([one_character 17 | for one_character in password 18 | if one_character in uppercase_set]) < min_uppercase: 19 | print(f'Not enough uppercase letters; min is {min_uppercase}') 20 | return False 21 | elif len([one_character 22 | for one_character in password 23 | if one_character in lowercase_set]) < min_lowercase: 24 | print(f'Not enough lowercase letters; min is {min_lowercase}') 25 | return False 26 | elif len([one_character 27 | for one_character in password 28 | if one_character in punctuation_set]) < min_punctuation: 29 | print(f'Not enough punctuation; min is {min_punctuation}') 30 | return False 31 | elif len([one_character 32 | for one_character in password 33 | if one_character in digits_set]) < min_digits: 34 | print(f'Not enough digits; min is {min_digits}') 35 | return False 36 | else: 37 | return True 38 | return check_password 39 | -------------------------------------------------------------------------------- /ch06-functions/e27b2_getitem.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 6, exercise 27, beyond 2: getitem""" 4 | 5 | 6 | def getitem(index): 7 | def inner(data): 8 | return data[index] 9 | return inner 10 | -------------------------------------------------------------------------------- /ch06-functions/e27b3_doboth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 6, exercise 27, beyond 3: doboth""" 4 | 5 | 6 | def doboth(f1, f2): 7 | def inner(data): 8 | return f2(f1(data)) 9 | return inner 10 | -------------------------------------------------------------------------------- /ch06-functions/test_e25_xml.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from e25_xml import myxml 3 | 4 | 5 | def test_tagonly(): 6 | assert myxml('tagname') == '' 7 | 8 | 9 | def test_tag_simple_text(): 10 | assert myxml('tagname', 'text') == 'text' 11 | 12 | 13 | def test_nested(): 14 | assert myxml('a', 15 | myxml('b', 16 | myxml('c', 'text'))) == 'text' 17 | 18 | 19 | def test_attributes(): 20 | assert myxml('tagname', 21 | a=1, b=2, c=3) == '' 22 | 23 | 24 | def test_attributes_and_text(): 25 | assert myxml('tagname', 'text', 26 | a=1, b=2, c=3) == 'text' 27 | -------------------------------------------------------------------------------- /ch06-functions/test_e26_calc.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from e26_calc import calc 3 | 4 | 5 | @pytest.mark.parametrize('to_solve, output', [ 6 | ('+ 2 2', 4), 7 | ('- 2 3', -1), 8 | ('* 2 4', 8), 9 | ('/ 5 2', 2.5), 10 | ('** 2 6', 64), 11 | ('% 7 2', 1), 12 | ]) 13 | def test_simple(to_solve, output): 14 | assert calc(to_solve) == output 15 | -------------------------------------------------------------------------------- /ch06-functions/test_e27_makepw.py: -------------------------------------------------------------------------------- 1 | from e27_makepw import create_password_generator 2 | import random 3 | import pytest 4 | import string 5 | 6 | 7 | @pytest.mark.parametrize('pool, size, pw', [ 8 | ('abcdef', 8, 'ddaceddc'), 9 | ('!@#$%', 8, '$$!#%$$#'), 10 | (string.ascii_lowercase, 20, 'mynbiqpmzjplsgqejeyd') 11 | ]) 12 | def test_simple(pool, size, pw): 13 | random.seed(0) 14 | create_password = create_password_generator(pool) 15 | output = create_password(size) 16 | assert len(output) == size 17 | assert output == pw 18 | -------------------------------------------------------------------------------- /ch07-comprehensions/.ipynb_checkpoints/Chapter 7 — Comprehensions-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 4 6 | } 7 | -------------------------------------------------------------------------------- /ch07-comprehensions/e28_join_numbers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 28: join_numbers""" 4 | 5 | 6 | def join_numbers(numbers): 7 | """Takes an iterable of numbers as input. 8 | Output is a string containing the numbers in that iterable, 9 | separated by commas. 10 | """ 11 | return ','.join(str(number) 12 | for number in numbers) 13 | -------------------------------------------------------------------------------- /ch07-comprehensions/e28b1_under_10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 28, beyond 1: join_under_10""" 4 | 5 | 6 | def join_under_10(numbers): 7 | return ','.join(str(number) 8 | for number in numbers 9 | if 0 <= number <= 10) 10 | -------------------------------------------------------------------------------- /ch07-comprehensions/e28b2_sum_hexes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 28, beyond 2: sum_hexes""" 4 | 5 | 6 | def sum_hexes(hex_numbers): 7 | return sum(int(one_number, 16) 8 | for one_number in hex_numbers) 9 | -------------------------------------------------------------------------------- /ch07-comprehensions/e28b3_reverse_words.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 28, beyond 3: reverse_words""" 4 | 5 | 6 | def reverse_words(filename): 7 | return [' '.join(reversed(one_line.split())) 8 | for one_line in open(filename)] 9 | -------------------------------------------------------------------------------- /ch07-comprehensions/e29_sum_numbers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 29: sum_numbers""" 4 | 5 | 6 | def sum_numbers(numbers): 7 | """Takes a string containing space-separated words. 8 | Output is an integer, the sum of those words that can 9 | be turned into integers. 10 | """ 11 | return sum(int(number) 12 | for number in numbers.split() 13 | if number.isdigit()) 14 | -------------------------------------------------------------------------------- /ch07-comprehensions/e29b1_1v20c_lines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 29, beyond 1: lines_with_1v20c""" 4 | 5 | 6 | def lines_with_1v20c(filename): 7 | return [one_line 8 | for one_line in open(filename) 9 | if len(one_line) >= 20 and 10 | len(set('aeiou') & set(one_line)) >= 1] 11 | -------------------------------------------------------------------------------- /ch07-comprehensions/e29b2_increment_area_code.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 29, beyond 2: increment_area_code""" 4 | 5 | 6 | def increment_area_code(full_phone_number): 7 | area_code, phone_number = full_phone_number.split('-', 1) 8 | 9 | if area_code[0] in '012345': 10 | area_code = str(int(area_code) + 1) 11 | 12 | return f'{area_code}-{phone_number}' 13 | 14 | 15 | def increment_all_area_codes(area_codes): 16 | return [increment_area_code(one_phone_number) 17 | for one_phone_number in area_codes] 18 | -------------------------------------------------------------------------------- /ch07-comprehensions/e29b3_age_in_months.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 29, beyond 3: age_in_months""" 4 | 5 | 6 | def age_in_months(list_of_people): 7 | return [dict(**one_person, age_in_months=one_person['age'] * 12) 8 | for one_person in mylist 9 | if one_person['age'] <= 20] 10 | -------------------------------------------------------------------------------- /ch07-comprehensions/e30_flatten_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 29: flatten""" 4 | 5 | 6 | def flatten(mylist): 7 | """Expects to get a nested list (a list of lists) 8 | as input. Returns a flattened list, containing the 9 | elements of mylist in order, as output. 10 | """ 11 | return [one_element 12 | for one_sublist in mylist 13 | for one_element in one_sublist] 14 | -------------------------------------------------------------------------------- /ch07-comprehensions/e30b1_flatten_odd_ints.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 29, beyond 1: flatten_odd_ints""" 4 | 5 | 6 | def flatten_odd_ints(mylist): 7 | return [int(str(one_element)) 8 | for one_sublist in mylist 9 | for one_element in one_sublist 10 | if str(one_element).strip().isdigit() and int(one_element) % 2 == 1] 11 | -------------------------------------------------------------------------------- /ch07-comprehensions/e30b2_grandchildren_names.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 29, beyond 2: grandchildren_names""" 4 | 5 | 6 | def grandchildren_names(d): 7 | return [one_grandchild 8 | for grandchild_list in d.values() 9 | for one_grandchild in grandchild_list] 10 | -------------------------------------------------------------------------------- /ch07-comprehensions/e30b3_sorted_grandchildren.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 29, beyond 3: sorted_grandchildren""" 4 | 5 | 6 | import operator 7 | 8 | 9 | def sorted_grandchildren(d): 10 | grandchildren = [one_grandchild 11 | for one_grandchild_list in d.values() 12 | for one_grandchild in one_grandchild_list] 13 | 14 | return [one_grandchild['name'] 15 | for one_grandchild in sorted(grandchildren, 16 | key=operator.itemgetter('age'))] 17 | -------------------------------------------------------------------------------- /ch07-comprehensions/e31_pig_latin_file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 31: plfile""" 4 | 5 | 6 | def plword(word): 7 | """Takes a string as input. It should be a single 8 | word. Returns a string, the input word translated into 9 | Pig Latin. 10 | """ 11 | if word[0] in 'aeiou': 12 | return word + 'way' 13 | 14 | return word[1:] + word[0] + 'ay' 15 | 16 | 17 | def plfile(filename): 18 | """Takes a filename as input. Returns a string 19 | containing the file's contents, with each word 20 | translated into Pig Latin. 21 | """ 22 | return ' '.join(plword(one_word) 23 | for one_line in open(filename) 24 | for one_word in one_line.split()) 25 | -------------------------------------------------------------------------------- /ch07-comprehensions/e31b1_funcfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 31, beyond 1: funcfile""" 4 | 5 | 6 | def funcfile(filename, func): 7 | return ' '.join(func(one_word) 8 | for one_line in open(filename) 9 | for one_word in one_line.split()) 10 | -------------------------------------------------------------------------------- /ch07-comprehensions/e31b2_dicts_to_tuples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 31, beyond 2: dicts_to_tuples""" 4 | 5 | 6 | def dicts_to_tuples(list_of_dicts): 7 | return [one_tuple 8 | for one_dict in list_of_dicts 9 | for one_tuple in one_dict.items()] 10 | -------------------------------------------------------------------------------- /ch07-comprehensions/e31b3_most_popular_hobbies.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 31, beyond 3: most_popular_hobbies""" 4 | 5 | 6 | import collections 7 | 8 | 9 | def most_popular_hobbies(list_of_dicts): 10 | return collections.Counter([one_hobby 11 | for one_person in list_of_dicts 12 | for one_hobby in one_person['hobbies']]).most_common(3) 13 | -------------------------------------------------------------------------------- /ch07-comprehensions/e32_flipped_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 32: flipped_dict""" 4 | 5 | 6 | def flipped_dict(a_dict): 7 | """Gets a dict as an argument. 8 | Returns a dict as output. The output dict's keys 9 | are the input dict's values, and vice versa. 10 | """ 11 | return {value: key 12 | for key, value in a_dict.items()} 13 | -------------------------------------------------------------------------------- /ch07-comprehensions/e32b1_word_vowels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 32, beyond 1: word_vowels""" 4 | 5 | 6 | def vowel_count(word): 7 | total = 0 8 | for one_letter in word.lower(): 9 | if one_letter in 'aeiou': 10 | total += 1 11 | return total 12 | 13 | 14 | def word_vowels(s): 15 | return {one_word: vowel_count(one_word) 16 | for one_word in s.split()} 17 | -------------------------------------------------------------------------------- /ch07-comprehensions/e32b2_file_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 32, beyond 2: file_info""" 4 | 5 | import glob 6 | import os 7 | 8 | 9 | def file_info(dirname): 10 | return {one_filename: os.stat(one_filename).st_size 11 | for one_filename in glob.glob(f'{dirname}/*') 12 | if os.path.isfile(one_filename)} 13 | -------------------------------------------------------------------------------- /ch07-comprehensions/e32b3_read_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 32, beyond 3: read_config""" 4 | 5 | 6 | def read_config(filename): 7 | return {one_line.split('=')[0]: one_line.split('=')[1].strip() 8 | for one_line in open(filename)} 9 | -------------------------------------------------------------------------------- /ch07-comprehensions/e33_transform_values.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 33: transform_values""" 4 | 5 | 6 | def transform_values(func, a_dict): 7 | """Takes two arguments, a function and a dict. 8 | Returns a dict in which the keys are the original 9 | dict's keys, but the values are the result of invoking 10 | the function on each original value. 11 | """ 12 | return {key: func(value) 13 | for key, value in a_dict.items()} 14 | -------------------------------------------------------------------------------- /ch07-comprehensions/e33b1_transform_values2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 33, beyond 1: transform_values2""" 4 | 5 | 6 | def transform_values2(func1, func2, a_dict): 7 | return {key: func(value) 8 | for key, value in a_dict.items() 9 | if func2(key, value)} 10 | -------------------------------------------------------------------------------- /ch07-comprehensions/e33b2_passwd_to_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 33, beyond 2: file_info""" 4 | 5 | # Yes, this is a duplicate of exercise 33, beyond 2! Whoops... 6 | 7 | import glob 8 | import os 9 | 10 | 11 | def file_info(dirname): 12 | return {one_filename: os.stat(one_filename).st_size 13 | for one_filename in glob.glob(f'{dirname}/*') 14 | if os.path.isfile(one_filename)} 15 | -------------------------------------------------------------------------------- /ch07-comprehensions/e33b3_file_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 32, beyond 3: file_info""" 4 | 5 | 6 | def vowel_count(word): 7 | total = 0 8 | for one_letter in word.lower(): 9 | if one_letter in 'aeiou': 10 | total += 1 11 | return total 12 | 13 | 14 | def word_vowels(s): 15 | return {one_word: vowel_count(one_word) 16 | for one_word in s.split()} 17 | -------------------------------------------------------------------------------- /ch07-comprehensions/e34_supervocalic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 34: get_sv""" 4 | 5 | 6 | def get_sv(filename): 7 | """Given a filename (string) as input, 8 | this function returns a set of all words 9 | in which all five vowels can be found. 10 | """ 11 | vowels = {'a', 'e', 'i', 'o', 'u'} 12 | 13 | return {word.strip() 14 | for word in open(filename) 15 | if vowels < set(word.lower())} 16 | -------------------------------------------------------------------------------- /ch07-comprehensions/e34b1_different_shells.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 34, beyond 1: different_shells""" 4 | 5 | 6 | def different_shells(filename): 7 | return {one_line.split(':')[-1].strip() 8 | for one_line in open(filename) 9 | if not one_line.startswith(('\n', '#'))} 10 | -------------------------------------------------------------------------------- /ch07-comprehensions/e34b2_word_lengths.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 34, beyond 2: word_lengths""" 4 | 5 | 6 | def word_lengths(filename): 7 | return {len(one_word) 8 | for one_line in open(filename) 9 | for one_word in one_line.split()} 10 | -------------------------------------------------------------------------------- /ch07-comprehensions/e34b3_letters_in_names.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 34, beyond 3: letters_in_names""" 4 | 5 | 6 | import string 7 | 8 | 9 | def letters_in_names(list_of_names): 10 | return {one_letter 11 | for one_letter in ''.join(list_of_names) 12 | if one_letter in string.ascii_letters} 13 | -------------------------------------------------------------------------------- /ch07-comprehensions/e35a_gematria_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 35a: gematria_dict""" 4 | 5 | import string 6 | 7 | 8 | def gematria_dict(): 9 | """Function that returns a dictionary of ASCII values 10 | for all lowercase letters. The keys are the letters, and 11 | the values are the numbers, starting with 1 for 'a'. 12 | """ 13 | return {char: index 14 | for index, char in enumerate(string.ascii_lowercase, 1)} 15 | -------------------------------------------------------------------------------- /ch07-comprehensions/e35ab1_read_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 35a, beyond 1: read_config""" 4 | 5 | # Yes, this is a duplicate of e32 b3! Sorry! 6 | 7 | 8 | def read_config(filename): 9 | return {one_line.split('=')[0]: one_line.split('=')[1].strip() 10 | for one_line in open(filename)} 11 | -------------------------------------------------------------------------------- /ch07-comprehensions/e35ab2_read_config_int.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 35a, beyond 2: read_config_int""" 4 | 5 | 6 | def read_config(filename): 7 | return {one_line.split('=')[0]: int(one_line.split('=')[1].strip()) 8 | for one_line in open(filename) 9 | if one_line.split('=')[1].strip().isdigit()} 10 | -------------------------------------------------------------------------------- /ch07-comprehensions/e35ab3_cities.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 35a, beyond 3: cities""" 4 | 5 | import json 6 | 7 | 8 | def get_city_data(filename): 9 | return {one_city['city']: one_city['population'] 10 | for one_city in json.load(open(filename)) 11 | } 12 | 13 | 14 | def get_city_data2(filename): 15 | return {(one_city['city'], one_city['state']): one_city['population'] 16 | for one_city in json.load(open(filename)) 17 | } 18 | -------------------------------------------------------------------------------- /ch07-comprehensions/e35b_gematria_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 35b: gematria_equal_words""" 4 | 5 | from e35a_gematria_1 import gematria_dict 6 | 7 | GEMATRIA = gematria_dict() 8 | 9 | 10 | def gematria_for(word): 11 | """Function that calculates the gematria 12 | for a given word, an argument passed as a string. 13 | """ 14 | return sum(GEMATRIA.get(one_char, 0) 15 | for one_char in word) 16 | 17 | 18 | def gematria_equal_words(input_word): 19 | """Function that takes a string (word) as input, 20 | and returns a list of strings (words) whose calculated 21 | gematria is identical. 22 | """ 23 | our_score = gematria_for(input_word.lower()) 24 | return [one_word.strip() 25 | for one_word in open('/usr/share/dict/words') 26 | if gematria_for(one_word.lower()) == our_score] 27 | -------------------------------------------------------------------------------- /ch07-comprehensions/e35bb1_temp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 35b, beyond 1: temperature""" 4 | 5 | 6 | def dict_f_to_c(dict_of_temps): 7 | return {key: (value-32)/1.8 8 | for key, value in dict_of_temps.items()} 9 | -------------------------------------------------------------------------------- /ch07-comprehensions/e35bb2_books.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 35b, beyond 2: books""" 4 | 5 | 6 | def make_book_dict(books): 7 | 8 | return {title: {'first': name.split()[0], 9 | 'last': name.split()[1], 10 | 'price': price} 11 | for name, title, price in books} 12 | -------------------------------------------------------------------------------- /ch07-comprehensions/e35bb3_currency_conversion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 7, exercise 35b, beyond 3: currency_conversion""" 4 | 5 | 6 | def currency_conversion(books, new_currency): 7 | return {title: {'first': name.split()[0], 8 | 'last': name.split()[1], 9 | 'price': price * conversions[new_currency]} 10 | for name, title, price in books} 11 | -------------------------------------------------------------------------------- /ch07-comprehensions/test_e28_join_numbers.py: -------------------------------------------------------------------------------- 1 | from e28_join_numbers import join_numbers 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('seq, string', [ 6 | (range(15), '0,1,2,3,4,5,6,7,8,9,10,11,12,13,14'), 7 | (range(10, 15), '10,11,12,13,14'), 8 | (range(0), '') 9 | ]) 10 | def test_join_numbers(seq, string): 11 | assert join_numbers(seq) == string 12 | -------------------------------------------------------------------------------- /ch07-comprehensions/test_e29_sum_numbers.py: -------------------------------------------------------------------------------- 1 | from e29_sum_numbers import sum_numbers 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('numbers, total', [ 6 | ('10 20 30', 60), 7 | ('10 abc 30', 40), 8 | ('10 abc 20 de44 30 55fg 40', 100) 9 | ]) 10 | def test_sum_numbers(numbers, total): 11 | assert sum_numbers(numbers) == total 12 | -------------------------------------------------------------------------------- /ch07-comprehensions/test_e30_flatten_list.py: -------------------------------------------------------------------------------- 1 | from e30_flatten_list import flatten 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('nested, output', [ 6 | ([[10, 20, 30]], [10, 20, 30]), 7 | ([[10, 20], [30, 40]], [10, 20, 30, 40]), 8 | ]) 9 | def test_flatten(nested, output): 10 | assert flatten(nested) == output 11 | -------------------------------------------------------------------------------- /ch07-comprehensions/test_e31_pig_latin_file.py: -------------------------------------------------------------------------------- 1 | from e31_pig_latin_file import plword, plfile 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('word, pword', [ 6 | ('test', 'esttay'), 7 | ('octopus', 'octopusway'), 8 | ('papaya', 'apayapay') 9 | ]) 10 | def test_plword(word, pword): 11 | assert plword(word) == pword 12 | 13 | 14 | @pytest.fixture 15 | def simple_file(tmp_path): 16 | f = tmp_path / 'filename.txt' 17 | f.write_text('this is a test\nof my translation program\n') 18 | return f 19 | 20 | 21 | def test_simple(simple_file): 22 | assert(plfile(simple_file) 23 | ) == 'histay isway away esttay ofway ymay ranslationtay rogrampay' 24 | -------------------------------------------------------------------------------- /ch07-comprehensions/test_e32_flipped_dict.py: -------------------------------------------------------------------------------- 1 | from e32_flipped_dict import flipped_dict 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('d, fd', [ 6 | ({}, {}), 7 | ({'a': 1}, {1: 'a'}), 8 | ({'a': 1, 'b': 2, 'c': 3}, {1: 'a', 2: 'b', 3: 'c'}), 9 | ({'a': 1, 'a': 2, 'a': 3}, {3: 'a'}) 10 | ]) 11 | def test_flipped_dict(d, fd): 12 | assert flipped_dict(d) == fd 13 | -------------------------------------------------------------------------------- /ch07-comprehensions/test_e33_transform_values.py: -------------------------------------------------------------------------------- 1 | from e33_transform_values import transform_values 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('f,d,output', [ 6 | (abs, {'a': 1, 'b': -2, 'c': 3}, {'a': 1, 'b': 2, 'c': 3}), 7 | (len, {'first': 'Reuven', 'last': 'Lerner'}, 8 | {'first': 6, 'last': 6}) 9 | ]) 10 | def test_transform_values(f, d, output): 11 | assert transform_values(f, d) == output 12 | -------------------------------------------------------------------------------- /ch07-comprehensions/test_e35a_gematria_1.py: -------------------------------------------------------------------------------- 1 | from e35a_gematria_1 import gematria_dict 2 | 3 | 4 | def test_gematria_dict(): 5 | d = gematria_dict() 6 | assert type(d) == dict 7 | assert len(d) == 26 8 | assert d['a'] == 1 9 | assert d['e'] == 5 10 | assert d['y'] == 25 11 | -------------------------------------------------------------------------------- /ch07-comprehensions/test_e35b_gematria_2.py: -------------------------------------------------------------------------------- 1 | from e35b_gematria_2 import gematria_equal_words, gematria_for 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('word, result', [ 6 | ('abc', 6), 7 | ('abcd', 10) 8 | ]) 9 | def test_gematria_for(word, result): 10 | assert gematria_for(word) == result 11 | 12 | 13 | def test_gematria_equal_words(): 14 | output = gematria_equal_words('taxi') 15 | assert len(output) == 1032 16 | assert 'search' in output 17 | assert 'pooh' in output 18 | assert 'zoid' in output 19 | -------------------------------------------------------------------------------- /ch08-modules/e36_freedonia.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 8, exercise 36: freedonia""" 4 | 5 | RATES = { 6 | 'Chico': 0.5, 7 | 'Groucho': 0.7, 8 | 'Harpo': 0.5, 9 | 'Zeppo': 0.4 10 | } 11 | 12 | 13 | def time_percentage(hour): 14 | """This function takes an integer from 0-24 and returns 15 | the percentage of the day, as a float, that has passed at that hour. 16 | """ 17 | return hour / 24 18 | 19 | 20 | def calculate_tax(amount, state, hour): 21 | """This function returns the tax due in Freedonia based on the 22 | original amount, the state, and the hour at which the purchase was 23 | made. It returns the total amount, including the tax, that is due, 24 | as a float. 25 | """ 26 | return amount + (amount * RATES[state] * time_percentage(hour)) 27 | -------------------------------------------------------------------------------- /ch08-modules/e36b1_tax_brackets.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 8, exercise 36, beyond 1: tax_brackets""" 4 | 5 | 6 | brackets = [{'start': 0, 'end': 1000, 'tax': 0}, 7 | {'start': 1000, 'end': 10000, 'tax': .1}, 8 | {'start': 10000, 'end': 20000, 'tax': .2}, 9 | {'start': 20000, 'end': 999999999999, 'tax': .5}] 10 | 11 | 12 | def tax_brackets(amount, brackets): 13 | tax_owed = 0 14 | 15 | for one_bracket in brackets: 16 | if amount < one_bracket['start']: 17 | continue 18 | 19 | taxed_amount = min(amount, one_bracket['end']) 20 | taxed_amount -= one_bracket['start'] 21 | 22 | tax_owed += taxed_amount * one_bracket['tax'] 23 | 24 | return tax_owed 25 | -------------------------------------------------------------------------------- /ch08-modules/e36b2_analyze_string.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 8, exercise 36, beyond 2: analyze_string""" 4 | 5 | 6 | def analyze_string(s): 7 | output = {'isdigit': 0, 8 | 'isalpha': 0 9 | 'isspace': 0} 10 | 11 | for one_character in s: 12 | for methodname in output: 13 | if getattr(one_character, methodname)(): # a sneaky, cool trick! 14 | output[methodname] += 1 15 | 16 | return output 17 | -------------------------------------------------------------------------------- /ch08-modules/e36b3_fromkeys_func.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 8, exercise 36, beyond 3: fromkeys_func""" 4 | 5 | 6 | def fromkeys_func(s, func): 7 | output = {} 8 | 9 | for one_item in s: 10 | output[one_item] = func(one_item) 11 | 12 | return output 13 | -------------------------------------------------------------------------------- /ch08-modules/e37_menu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 8, exercise 37: menu""" 4 | 5 | 6 | def menu(**options): 7 | """Function that takes keyword arguments. The value 8 | associated with each key is a function taking zero arguments. 9 | 10 | The user is asked to enter input. 11 | 12 | If the input matches a keyword, then the associated function 13 | is invoked, and its return value is returned to the user. 14 | 15 | If the input doesn't match a keywork, the user is asked to 16 | try again. 17 | """ 18 | while True: 19 | option_string = '/'.join(sorted(options)) 20 | choice = input(f'Enter an option ({option_string}): ') 21 | if choice in options: 22 | return options[choice]() 23 | 24 | print('Not a valid option') 25 | -------------------------------------------------------------------------------- /ch08-modules/e37b1_selftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 8, exercise 37, beyond 1: selftest""" 4 | 5 | import subprocess 6 | import sys 7 | from io import StringIO 8 | 9 | 10 | def menu(**options): 11 | while True: 12 | option_string = '/'.join(sorted(options)) 13 | choice = input(f'Enter an option ({option_string}): ') 14 | if choice in options: 15 | return options[choice]() 16 | 17 | print('Not a valid option') 18 | 19 | 20 | def test_good_input(monkeypatch): 21 | monkeypatch.setattr('sys.stdin', StringIO('a\n')) 22 | 23 | def a(): 24 | return 'called a' 25 | 26 | returned_value = menu(a=a) 27 | 28 | assert 'called a' in returned_value 29 | 30 | 31 | def test_bad_then_good_input(monkeypatch, capsys): 32 | monkeypatch.setattr('sys.stdin', StringIO('q\na\n')) 33 | 34 | def a(): 35 | return 'called a' 36 | 37 | returned_value = menu(a=a) 38 | captured_stdout, captured_stderr = capsys.readouterr() 39 | 40 | assert 'Not a valid option' in captured_stdout 41 | assert 'called a' in returned_value 42 | 43 | 44 | if __name__ == '__main__': 45 | program_name = sys.argv[0] 46 | 47 | subprocess.run(f'pytest -vv {program_name}', shell=True) 48 | -------------------------------------------------------------------------------- /ch08-modules/e37b2_menu/README.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reuven/python-workout/b9c68520d572bf70eff8e554a8ee9c8702c88e6e/ch08-modules/e37b2_menu/README.rst -------------------------------------------------------------------------------- /ch08-modules/e37b2_menu/e37b2_menu/__init__.py: -------------------------------------------------------------------------------- 1 | from .e37b2_menu import menu 2 | 3 | __version__ = '0.1.0' 4 | -------------------------------------------------------------------------------- /ch08-modules/e37b2_menu/e37b2_menu/e37b2_menu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 8, exercise 37, beyond 2: selftest (in a package)""" 4 | 5 | 6 | def menu(**options): 7 | while True: 8 | option_string = '/'.join(sorted(options)) 9 | choice = input(f'Enter an option ({option_string}): ') 10 | if choice in options: 11 | return options[choice]() 12 | 13 | print('Not a valid option') 14 | -------------------------------------------------------------------------------- /ch08-modules/e37b2_menu/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "e37b2_menu" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Reuven Lerner "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.8" 9 | 10 | [tool.poetry.dev-dependencies] 11 | pytest = "^5.2" 12 | 13 | [build-system] 14 | requires = ["poetry-core>=1.0.0"] 15 | build-backend = "poetry.core.masonry.api" 16 | -------------------------------------------------------------------------------- /ch08-modules/e37b2_menu/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reuven/python-workout/b9c68520d572bf70eff8e554a8ee9c8702c88e6e/ch08-modules/e37b2_menu/tests/__init__.py -------------------------------------------------------------------------------- /ch08-modules/e37b2_menu/tests/test_e37b2_menu.py: -------------------------------------------------------------------------------- 1 | from e37b2_menu import menu, __version__ 2 | 3 | import sys 4 | from io import StringIO 5 | 6 | 7 | def test_version(): 8 | assert __version__ == '0.1.0' 9 | 10 | 11 | def test_good_input(monkeypatch): 12 | monkeypatch.setattr('sys.stdin', StringIO('a\n')) 13 | 14 | def a(): 15 | return 'called a' 16 | 17 | returned_value = menu(a=a) 18 | 19 | assert 'called a' in returned_value 20 | 21 | 22 | def test_bad_then_good_input(monkeypatch, capsys): 23 | monkeypatch.setattr('sys.stdin', StringIO('q\na\n')) 24 | 25 | def a(): 26 | return 'called a' 27 | 28 | returned_value = menu(a=a) 29 | captured_stdout, captured_stderr = capsys.readouterr() 30 | 31 | assert 'Not a valid option' in captured_stdout 32 | assert 'called a' in returned_value 33 | -------------------------------------------------------------------------------- /ch08-modules/e37b3_stuff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 8, exercise 37, beyond 3: stuff""" 4 | 5 | __all__ = ['a', 'c', 'bar'] 6 | 7 | a = 100 8 | 9 | b = [10, 20, 30] 10 | 11 | c = {'a': 1, 'b': 2, 'c': 3} 12 | 13 | 14 | def foo(): 15 | return 'Hello from foo!' 16 | 17 | 18 | def bar(): 19 | return 'Hello from bar!' 20 | -------------------------------------------------------------------------------- /ch08-modules/screencasts/e36_freedonia.py: -------------------------------------------------------------------------------- 1 | RATES = { 2 | 'Chico': 0.5, 3 | 'Groucho': 0.7, 4 | 'Harpo': 0.5, 5 | 'Zeppo': 0.4 6 | } 7 | 8 | 9 | def time_percentage(hour): 10 | return hour / 24 11 | 12 | 13 | def calculate_tax(amount, state, hour): 14 | return amount + (amount * RATES[state] * time_percentage(hour)) 15 | -------------------------------------------------------------------------------- /ch08-modules/screencasts/menu.py: -------------------------------------------------------------------------------- 1 | def menu(**options): 2 | while True: 3 | option_string = '/'.join(sorted(options)) 4 | choice = input(f'Enter an option ({option_string}): ') 5 | 6 | if choice in options: 7 | return options[choice]() 8 | else: 9 | print(f'{choice} is not a valid option') 10 | -------------------------------------------------------------------------------- /ch08-modules/screencasts/use_freedonia.py: -------------------------------------------------------------------------------- 1 | from e36_freedonia import calculate_tax 2 | 3 | print(calculate_tax(100, 'Harpo', 15)) 4 | print(calculate_tax(100, 'Harpo', 18)) 5 | print(calculate_tax(100, 'Groucho', 20)) 6 | print(calculate_tax(100, 'Groucho', 22)) 7 | print(calculate_tax(100, 'Chico', 8)) 8 | print(calculate_tax(100, 'Chico', 20)) 9 | -------------------------------------------------------------------------------- /ch08-modules/screencasts/use_menu.py: -------------------------------------------------------------------------------- 1 | from menu import menu 2 | 3 | 4 | def func_a(): 5 | return 'A' 6 | 7 | 8 | def func_b(): 9 | return 'B' 10 | 11 | 12 | return_value = menu(a=func_a, b=func_b) 13 | print(f'Return value is {return_value}') 14 | -------------------------------------------------------------------------------- /ch08-modules/test_e36_freedonia.py: -------------------------------------------------------------------------------- 1 | from e36_freedonia import time_percentage, calculate_tax 2 | import pytest 3 | from math import isclose 4 | 5 | 6 | @pytest.mark.parametrize('hour,percentage', [ 7 | (0, 0), 8 | (12, 0.5), 9 | (18, 0.75), 10 | (23, 0.958) 11 | ]) 12 | def test_time_percentage(hour, percentage): 13 | assert isclose(time_percentage(hour), percentage, rel_tol=0.05) 14 | 15 | 16 | @pytest.mark.parametrize('amount,state,hour,tax', [ 17 | (500, 'Harpo', 12, 625), 18 | (500, 'Harpo', 21, 718), 19 | ]) 20 | def test_calculate_tax(amount, state, hour, tax): 21 | assert isclose(calculate_tax(amount, state, hour), tax, rel_tol=0.05) 22 | -------------------------------------------------------------------------------- /ch08-modules/test_e37_menu.py: -------------------------------------------------------------------------------- 1 | from e37_menu import menu 2 | from io import StringIO 3 | import pytest 4 | 5 | 6 | def funca(): 7 | return 'I am funca' 8 | 9 | 10 | def funcb(): 11 | return 'I am funcb' 12 | 13 | 14 | @pytest.mark.parametrize('choice,output', [ 15 | ('a', 'I am funca'), 16 | ('b', 'I am funcb') 17 | ]) 18 | def test_choices(monkeypatch, capsys, choice, output): 19 | monkeypatch.setattr('sys.stdin', StringIO(f'{choice}\n')) 20 | output = menu(a=funca, b=funcb) 21 | captured_out, captured_err = capsys.readouterr() 22 | 23 | assert 'a/b' in captured_out 24 | assert output.endswith(output) 25 | 26 | 27 | def test_bad_choice(monkeypatch, capsys): 28 | monkeypatch.setattr('sys.stdin', StringIO('c\na\n')) 29 | output = menu(a=funca, b=funcb) 30 | captured_out, captured_err = capsys.readouterr() 31 | 32 | assert 'a/b' in captured_out 33 | assert 'Not a valid option' in captured_out 34 | assert output.endswith('I am funca') 35 | -------------------------------------------------------------------------------- /ch09-objects/e38_scoop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 38: scoop""" 4 | 5 | 6 | class Scoop(): 7 | """Class representing a single scoop of ice cream. 8 | The sole attribute is "flavor", a string. 9 | """ 10 | 11 | def __init__(self, flavor): 12 | self.flavor = flavor 13 | 14 | 15 | def create_scoops(): 16 | """Function that creates three scoops, puts them 17 | in a list, and iterates over that list, printing the 18 | flavors. 19 | """ 20 | scoops = [Scoop('chocolate'), 21 | Scoop('vanilla'), 22 | Scoop('persimmon')] 23 | for scoop in scoops: 24 | print(scoop.flavor) 25 | -------------------------------------------------------------------------------- /ch09-objects/e38b1_beverage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 38, beyond 1: beverage""" 4 | 5 | 6 | class Beverage: 7 | def __init__(self, name, temp): 8 | self.name = name 9 | self.temp = temp 10 | -------------------------------------------------------------------------------- /ch09-objects/e38b2_beverage_default_temp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 38, beyond 2: beverage_default_temp""" 4 | 5 | 6 | class Beverage: 7 | def __init__(self, name, temp=75): 8 | self.name = name 9 | self.temp = temp 10 | -------------------------------------------------------------------------------- /ch09-objects/e38b3_logfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 38, beyond 3: logfile""" 4 | 5 | 6 | class Logfile: 7 | def __init__(self, filename): 8 | self.file = open(filename, 'w') 9 | -------------------------------------------------------------------------------- /ch09-objects/e39_bowl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 39: bowl""" 4 | 5 | 6 | class Scoop(): 7 | """Class representing a single scoop of ice cream. 8 | The sole attribute is "flavor", a string. 9 | """ 10 | 11 | def __init__(self, flavor): 12 | self.flavor = flavor 13 | 14 | 15 | class Bowl(): 16 | """Class representing a bowl of ice cream. 17 | 18 | The "scoops" attribute is a list, containing scoops. 19 | You can add one or more scoops with the "add_scoops" method. 20 | """ 21 | 22 | def __init__(self): 23 | self.scoops = [] 24 | 25 | def add_scoops(self, *new_scoops): 26 | """Add one or more scoops to the bowl""" 27 | for one_scoop in new_scoops: 28 | self.scoops.append(one_scoop) 29 | 30 | def __repr__(self): 31 | return '\n'.join(s.flavor for s in self.scoops) 32 | -------------------------------------------------------------------------------- /ch09-objects/e39b1_book_and_shelf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 39, beyond 1: book_and_shelf""" 4 | 5 | 6 | class Book: 7 | def __init__(self, title, author, price): 8 | self.title = title 9 | self.author = author 10 | self.price = price 11 | 12 | 13 | class Shelf: 14 | def __init__(self): 15 | self.books = [] 16 | 17 | def add_books(self, *args): 18 | self.books += args 19 | 20 | def total_price(self): 21 | return sum(one_book.price 22 | for one_book in self.books) 23 | -------------------------------------------------------------------------------- /ch09-objects/e39b2_has_book.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 39, beyond 2: has_book""" 4 | 5 | 6 | class Book: 7 | def __init__(self, title, author, price): 8 | self.title = title 9 | self.author = author 10 | self.price = price 11 | 12 | 13 | class Shelf: 14 | def __init__(self): 15 | self.books = [] 16 | 17 | def add_books(self, *args): 18 | self.books += args 19 | 20 | def total_price(self): 21 | return sum(one_book.price 22 | for one_book in self.books) 23 | 24 | def has_book(self, title): 25 | return title in (one_book.title 26 | for one_book in self.books) 27 | -------------------------------------------------------------------------------- /ch09-objects/e39b3_book_width.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 39, beyond 2: has_book""" 4 | 5 | 6 | class TooManyBookOnShelfError(Exception): 7 | pass 8 | 9 | 10 | class Book: 11 | def __init__(self, title, author, price, width): 12 | self.title = title 13 | self.author = author 14 | self.price = price 15 | self.width = width 16 | 17 | 18 | class Shelf: 19 | def __init__(self, width): 20 | self.books = [] 21 | self.width = width 22 | 23 | def add_books(self, *args): 24 | for new_book in args: 25 | if self.total_width() + new_book.width > self.width: 26 | raise TooManyBookOnShelfError('Too many books!') 27 | self.books.append(new_book) 28 | 29 | def total_price(self): 30 | return sum(one_book.price 31 | for one_book in self.books) 32 | 33 | def has_book(self, title): 34 | return title in (one_book.title 35 | for one_book in self.books) 36 | 37 | def total_width(self): 38 | return sum(one_book.width 39 | for one_book in self.books) 40 | -------------------------------------------------------------------------------- /ch09-objects/e40_limited_size_bowl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 40: limited size bowl""" 4 | 5 | 6 | class Scoop(): 7 | """Class representing a single scoop of ice cream. 8 | The sole attribute is "flavor", a string. 9 | """ 10 | 11 | def __init__(self, flavor): 12 | self.flavor = flavor 13 | 14 | 15 | class Bowl(): 16 | """Class representing a bowl of ice cream. 17 | 18 | The class attribute max_scoops indicates the 19 | maximum number of scoops that the bowl can contain, 20 | assuming that the "add_scoops" method is used 21 | to add them. 22 | 23 | The "scoops" attribute is a list, containing scoops. 24 | You can add one or more scoops with the "add_scoops" method. 25 | """ 26 | max_scoops = 3 27 | 28 | def __init__(self): 29 | self.scoops = [] 30 | 31 | def add_scoops(self, *new_scoops): 32 | """Add one or more scoops to the bowl""" 33 | for one_scoop in new_scoops: 34 | if len(self.scoops) < Bowl.max_scoops: 35 | self.scoops.append(one_scoop) 36 | 37 | def __repr__(self): 38 | return '\n'.join(s.flavor for s in self.scoops) 39 | -------------------------------------------------------------------------------- /ch09-objects/e40b1_population.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 40, beyond 1: population""" 4 | 5 | 6 | class Person: 7 | population = 0 8 | 9 | def __init__(self, name): 10 | self.name = name 11 | Person.population += 1 12 | -------------------------------------------------------------------------------- /ch09-objects/e40b2_population_del.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 40, beyond 2: population_del""" 4 | 5 | 6 | class Person: 7 | population = 0 8 | 9 | def __init__(self, name): 10 | self.name = name 11 | Person.population += 1 12 | 13 | def __del__(self): 14 | Person.population -= 1 15 | -------------------------------------------------------------------------------- /ch09-objects/e40b3_transaction.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 40, beyond 3: transactions""" 4 | 5 | 6 | class Transaction: 7 | balance = 0 8 | 9 | def __init__(self, amount): 10 | self.amount = amount 11 | Transaction.balance += amount 12 | -------------------------------------------------------------------------------- /ch09-objects/e41_bigbowl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 41: big bowl""" 4 | 5 | 6 | class Scoop(): 7 | """Class representing a single scoop of ice cream. 8 | The sole attribute is "flavor", a string. 9 | """ 10 | 11 | def __init__(self, flavor): 12 | self.flavor = flavor 13 | 14 | 15 | class Bowl(): 16 | """Class representing a bowl of ice cream. 17 | 18 | The class attribute max_scoops indicates the 19 | maximum number of scoops that the bowl can contain, 20 | assuming that the "add_scoops" method is used 21 | to add them. 22 | 23 | The "scoops" attribute is a list, containing scoops. 24 | You can add one or more scoops with the "add_scoops" method. 25 | """ 26 | max_scoops = 3 27 | 28 | def __init__(self): 29 | self.scoops = [] 30 | 31 | def add_scoops(self, *new_scoops): 32 | """Add one or more scoops to the bowl""" 33 | for one_scoop in new_scoops: 34 | if len(self.scoops) < self.max_scoops: 35 | self.scoops.append(one_scoop) 36 | 37 | def __repr__(self): 38 | return '\n'.join(s.flavor for s in self.scoops) 39 | 40 | 41 | class BigBowl(Bowl): 42 | """Class representing a bigger bowl of ice cream.""" 43 | max_scoops = 5 44 | -------------------------------------------------------------------------------- /ch09-objects/e41b1_envelope.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 41, beyond 1: envelope""" 4 | 5 | 6 | class NotEnoughPostageError(Exception): 7 | pass 8 | 9 | 10 | class Envelope: 11 | postage_multiplier = 10 12 | 13 | def __init__(self, weight): 14 | self.weight = weight 15 | self.postage = 0 16 | self.was_sent = False 17 | 18 | def add_postage(self, amount): 19 | self.postage += amount 20 | 21 | def send(self): 22 | if self.postage >= self.weight * self.postage_multiplier: 23 | self.was_sent = True 24 | else: 25 | raise NotEnoughPostageError('Not enough postage') 26 | 27 | 28 | class BigEnvelope(Envelope): 29 | postage_multiplier = 15 30 | -------------------------------------------------------------------------------- /ch09-objects/e41b2_phone.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 41, beyond 2: phone""" 4 | 5 | 6 | class Phone: 7 | def __init__(self): 8 | pass 9 | 10 | def dial(self, number): 11 | return f'Dialing {number}' 12 | 13 | 14 | class SmartPhone(Phone): 15 | def run_app(self, app_name): 16 | return f'Running an app: {app_name}' 17 | 18 | 19 | class iPhone(SmartPhone): 20 | def run_app(self, app_name): 21 | return super().run_app(app_name).lower() 22 | -------------------------------------------------------------------------------- /ch09-objects/e41b3_bread.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 41, beyond 3: bread""" 4 | 5 | 6 | class Bread: 7 | def __init__(self): 8 | # nutrition per slice 9 | self.calories = 66 10 | self.carbs = 12 11 | self.sodium = 170 12 | self.sugar = 1 13 | self.fat = 0.8 14 | 15 | def get_nutrition(self, number_of_slices): 16 | return {key: value*number_of_slices 17 | for key, value in vars(self).items()} 18 | 19 | 20 | class WholeWheatBread(Bread): 21 | def __init__(self): 22 | self.calories = 67 23 | self.carbs = 12 24 | self.sodium = 138 25 | self.sugar = 1.4 26 | self.fat = 1 27 | 28 | 29 | class RyeBread(Bread): 30 | def __init__(self): 31 | self.calories = 67 32 | self.carbs = 12 33 | self.sodium = 172 34 | self.sugar = 1 35 | self.fat = 0.8 36 | -------------------------------------------------------------------------------- /ch09-objects/e42_flexible_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 42: flexible dict""" 4 | 5 | 6 | class FlexibleDict(dict): 7 | """Dict that lets you use a string or int somewhat interchangeably.""" 8 | 9 | def __getitem__(self, key): 10 | try: 11 | if key in self: 12 | pass 13 | elif str(key) in self: 14 | key = str(key) 15 | elif int(key) in self: 16 | key = int(key) 17 | except ValueError: 18 | pass 19 | 20 | return dict.__getitem__(self, key) 21 | -------------------------------------------------------------------------------- /ch09-objects/e42b1_string_key_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 42, beyond 1: string_key_dict""" 4 | 5 | 6 | class StringKeyDict(dict): 7 | def __setitem__(self, key, value): 8 | dict.__setitem__(self, str(key), value) 9 | -------------------------------------------------------------------------------- /ch09-objects/e42b2_recent_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 42, beyond 2: recent_dict""" 4 | 5 | 6 | class RecentDict(dict): 7 | def __init__(self, maxsize): 8 | super().__init__() 9 | self.maxsize = maxsize 10 | 11 | def __setitem__(self, key, value): 12 | dict.__setitem__(self, str(key), value) 13 | 14 | if len(self) > self.maxsize: 15 | self.pop(list(self.keys())[0]) 16 | -------------------------------------------------------------------------------- /ch09-objects/e42b3_flatlist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 42, beyond 3: flat_list""" 4 | 5 | 6 | class FlatList(list): 7 | def append(self, new_value): 8 | try: 9 | for one_item in new_value: 10 | list.append(self, one_item) 11 | except TypeError: 12 | list.append(self, new_value) 13 | -------------------------------------------------------------------------------- /ch09-objects/e43_animals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 43: animals""" 4 | 5 | 6 | class Animal(): 7 | """Base class for animals. Not meant to be instantiated.""" 8 | 9 | def __init__(self, color, number_of_legs): 10 | self.species = self.__class__.__name__ 11 | self.color = color 12 | self.number_of_legs = number_of_legs 13 | 14 | def __repr__(self): 15 | return f'{self.color} {self.species}, {self.number_of_legs} legs' 16 | 17 | 18 | class Wolf(Animal): 19 | """Class for creating 4-legged wolves of any color""" 20 | 21 | def __init__(self, color): 22 | super().__init__(color, 4) 23 | 24 | 25 | class Sheep(Animal): 26 | """Class for creating 4-legged sheep of any color""" 27 | 28 | def __init__(self, color): 29 | super().__init__(color, 4) 30 | 31 | 32 | class Snake(Animal): 33 | """Class for creating 0-legged snakes of any color""" 34 | 35 | def __init__(self, color): 36 | super().__init__(color, 0) 37 | 38 | 39 | class Parrot(Animal): 40 | """Class for creating 2-legged parrots of any color""" 41 | 42 | def __init__(self, color): 43 | super().__init__(color, 2) 44 | -------------------------------------------------------------------------------- /ch09-objects/e43b1_legged_animals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 43, beyond 1: legged_animals""" 4 | 5 | 6 | class Animal(): 7 | """Base class for animals. Not meant to be instantiated.""" 8 | 9 | def __init__(self, color): 10 | self.species = self.__class__.__name__ 11 | self.color = color 12 | 13 | def __repr__(self): 14 | return f'{self.color} {self.species}, {self.number_of_legs} legs' 15 | 16 | 17 | class TwoLeggedAnimal(Animal): 18 | def __init__(self, color): 19 | super().__init__(color) 20 | self.number_of_legs = 2 21 | 22 | 23 | class FourLeggedAnimal(Animal): 24 | def __init__(self, color): 25 | super().__init__(color) 26 | self.number_of_legs = 4 27 | 28 | 29 | class ZeroLeggedAnimal(Animal): 30 | def __init__(self, color): 31 | super().__init__(color) 32 | self.number_of_legs = 0 33 | 34 | 35 | class Wolf(FourLeggedAnimal): 36 | """Class for creating 4-legged wolves of any color""" 37 | 38 | def __init__(self, color): 39 | super().__init__(color) 40 | 41 | 42 | class Sheep(FourLeggedAnimal): 43 | """Class for creating 4-legged sheep of any color""" 44 | 45 | def __init__(self, color): 46 | super().__init__(color) 47 | 48 | 49 | class Snake(ZeroLeggedAnimal): 50 | """Class for creating 0-legged snakes of any color""" 51 | 52 | def __init__(self, color): 53 | super().__init__(color) 54 | 55 | 56 | class Parrot(TwoLeggedAnimal): 57 | """Class for creating 2-legged parrots of any color""" 58 | 59 | def __init__(self, color): 60 | super().__init__(color) 61 | -------------------------------------------------------------------------------- /ch09-objects/e43b2_class_legs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 43, beyond 2: class_legs""" 4 | 5 | 6 | class Animal(): 7 | """Base class for animals. Not meant to be instantiated.""" 8 | 9 | def __init__(self, color): 10 | self.species = self.__class__.__name__ 11 | self.color = color 12 | 13 | def __repr__(self): 14 | return f'{self.color} {self.species}, {self.number_of_legs} legs' 15 | 16 | 17 | class Wolf(Animal): 18 | """Class for creating 4-legged wolves of any color""" 19 | 20 | number_of_legs = 4 21 | 22 | def __init__(self, color): 23 | super().__init__(color) 24 | 25 | 26 | class Sheep(Animal): 27 | """Class for creating 4-legged sheep of any color""" 28 | 29 | number_of_legs = 4 30 | 31 | def __init__(self, color): 32 | super().__init__(color) 33 | 34 | 35 | class Snake(Animal): 36 | """Class for creating 0-legged snakes of any color""" 37 | 38 | number_of_legs = 0 39 | 40 | def __init__(self, color): 41 | super().__init__(color) 42 | 43 | 44 | class Parrot(Animal): 45 | """Class for creating 2-legged parrots of any color""" 46 | 47 | number_of_legs = 2 48 | 49 | def __init__(self, color): 50 | super().__init__(color) 51 | -------------------------------------------------------------------------------- /ch09-objects/e43b3_animal_noises.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 43, beyond 3: animal_noises""" 4 | 5 | 6 | class Animal(): 7 | """Base class for animals. Not meant to be instantiated.""" 8 | 9 | def __init__(self, color, number_of_legs): 10 | self.species = self.__class__.__name__ 11 | self.color = color 12 | self.number_of_legs = number_of_legs 13 | 14 | def __repr__(self): 15 | return f'{self.sound}--{self.color} {self.species}, {self.number_of_legs} legs' 16 | 17 | 18 | class Wolf(Animal): 19 | """Class for creating 4-legged wolves of any color""" 20 | 21 | sound = 'awooo' 22 | 23 | def __init__(self, color): 24 | super().__init__(color, 4) 25 | 26 | 27 | class Sheep(Animal): 28 | """Class for creating 4-legged sheep of any color""" 29 | 30 | sound = 'baa' 31 | 32 | def __init__(self, color): 33 | super().__init__(color, 4) 34 | 35 | 36 | class Snake(Animal): 37 | """Class for creating 0-legged snakes of any color""" 38 | 39 | sound = 'hiss' 40 | 41 | def __init__(self, color): 42 | super().__init__(color, 0) 43 | 44 | 45 | class Parrot(Animal): 46 | """Class for creating 2-legged parrots of any color""" 47 | 48 | sound = 'Polly wants a cracker!' 49 | 50 | def __init__(self, color): 51 | super().__init__(color, 2) 52 | -------------------------------------------------------------------------------- /ch09-objects/e44_cages.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 44: cages""" 4 | 5 | 6 | class Cage(): 7 | """Class for creating cages in which to put cute, furry animals.""" 8 | 9 | def __init__(self, id_number): 10 | self.id_number = id_number 11 | self.animals = [] 12 | 13 | def add_animals(self, *animals): 14 | """Add one or more animals to a cage. Returns None.""" 15 | for one_animal in animals: 16 | self.animals.append(one_animal) 17 | 18 | def __repr__(self): 19 | output = f'Cage {self.id_number}\n' 20 | output += '\n'.join('\t' + str(animal) 21 | for animal in self.animals) 22 | return output 23 | -------------------------------------------------------------------------------- /ch09-objects/e44b1_big_cage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 44, beyond 1: big_cage""" 4 | 5 | 6 | class Cage(): 7 | max_animals = 3 8 | 9 | def __init__(self, id_number): 10 | self.id_number = id_number 11 | self.animals = [] 12 | 13 | def add_animals(self, *animals): 14 | for one_animal in animals: 15 | if len(self.animals) < self.max_animals: 16 | self.animals.append(one_animal) 17 | 18 | def __repr__(self): 19 | output = f'Cage {self.id_number}\n' 20 | output += '\n'.join('\t' + str(animal) 21 | for animal in self.animals) 22 | return output 23 | 24 | 25 | class BigCage(Cage): 26 | max_animals = 5 27 | -------------------------------------------------------------------------------- /ch09-objects/e44b2_animal_space.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 44, beyond 2: animal_space""" 4 | 5 | 6 | class Animal(): 7 | def __init__(self, color, number_of_legs): 8 | self.species = self.__class__.__name__ 9 | self.color = color 10 | self.number_of_legs = number_of_legs 11 | 12 | def __repr__(self): 13 | return f'{self.color} {self.species}, {self.number_of_legs} legs' 14 | 15 | 16 | class Wolf(Animal): 17 | space_required = 10 18 | 19 | def __init__(self, color): 20 | super().__init__(color, 4) 21 | 22 | 23 | class Sheep(Animal): 24 | space_required = 5 25 | 26 | def __init__(self, color): 27 | super().__init__(color, 4) 28 | 29 | 30 | class Snake(Animal): 31 | space_required = 2 32 | 33 | def __init__(self, color): 34 | super().__init__(color, 0) 35 | 36 | 37 | class Parrot(Animal): 38 | space_required = 1 39 | 40 | def __init__(self, color): 41 | super().__init__(color, 2) 42 | 43 | 44 | class NotEnoughSpaceError(Exception): 45 | pass 46 | 47 | 48 | class Cage(): 49 | 50 | def __init__(self, id_number, total_space): 51 | self.id_number = id_number 52 | self.animals = [] 53 | self.total_space = total_space 54 | 55 | def add_animals(self, *animals): 56 | for one_animal in animals: 57 | if self.space_used() + one_animal.space_required > self.total_space: 58 | raise NotEnoughSpaceError( 59 | f'Not enough room for your {one_animal}') 60 | 61 | self.animals.append(one_animal) 62 | 63 | def space_used(self): 64 | return sum(one_animal.space_required 65 | for one_animal in self.animals) 66 | 67 | def __repr__(self): 68 | output = f'Cage {self.id_number}\n' 69 | output += '\n'.join('\t' + str(animal) 70 | for animal in self.animals) 71 | return output 72 | 73 | 74 | class BigCage(Cage): 75 | max_animals = 5 76 | -------------------------------------------------------------------------------- /ch09-objects/e44b3_animal_safety.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 44, beyond 3: animal_safety""" 4 | 5 | 6 | class Animal(): 7 | def __init__(self, color, number_of_legs): 8 | self.species = self.__class__.__name__ 9 | self.color = color 10 | self.number_of_legs = number_of_legs 11 | 12 | def __repr__(self): 13 | return f'{self.color} {self.species}, {self.number_of_legs} legs' 14 | 15 | 16 | class Wolf(Animal): 17 | 18 | def __init__(self, color): 19 | super().__init__(color, 4) 20 | 21 | 22 | class Sheep(Animal): 23 | 24 | def __init__(self, color): 25 | super().__init__(color, 4) 26 | 27 | 28 | class Snake(Animal): 29 | 30 | def __init__(self, color): 31 | super().__init__(color, 0) 32 | 33 | 34 | class Parrot(Animal): 35 | 36 | def __init__(self, color): 37 | super().__init__(color, 2) 38 | 39 | 40 | animal_safety = {Wolf: [Wolf, Snake, Parrot], 41 | Sheep: [Sheep, Snake, Parrot], 42 | Snake: [Wolf, Sheep], 43 | Parrot: [Wolf, Sheep]} 44 | 45 | 46 | class DangerousAssignmentError(Exception): 47 | pass 48 | 49 | 50 | class Cage(): 51 | 52 | def __init__(self, id_number): 53 | self.id_number = id_number 54 | self.animals = [] 55 | 56 | def add_animals(self, *animals): 57 | for one_animal in animals: 58 | for one_current_resident in self.animals: 59 | if type(one_animal) not in animal_safety[type(one_current_resident)]: 60 | raise DangerousAssignmentError( 61 | f'You cannot put a {type(one_animal)} with a {type(one_current_resident)}!') 62 | self.animals.append(one_animal) 63 | 64 | def __repr__(self): 65 | output = f'Cage {self.id_number}\n' 66 | output += '\n'.join('\t' + str(animal) 67 | for animal in self.animals) 68 | return output 69 | -------------------------------------------------------------------------------- /ch09-objects/e45_zoo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 45: zoo""" 4 | 5 | 6 | class Zoo(): 7 | """A class in which to place our animals.""" 8 | 9 | def __init__(self): 10 | self.cages = [] 11 | 12 | def add_cages(self, *cages): 13 | """Add one or more cages to our zoo""" 14 | for one_cage in cages: 15 | self.cages.append(one_cage) 16 | 17 | def __repr__(self): 18 | return '\n'.join(str(one_cage) 19 | for one_cage in self.cages) 20 | 21 | def animals_by_color(self, color): 22 | """Return a list of Animal objects whose 23 | color matches the requested color""" 24 | return [one_animal 25 | for one_cage in self.cages 26 | for one_animal in one_cage.animals 27 | if one_animal.color == color] 28 | 29 | def animals_by_legs(self, number_of_legs): 30 | """Return a list of Animal objects whose 31 | number of legs matches the requested number""" 32 | return [one_animal 33 | for one_cage in self.cages 34 | for one_animal in one_cage.animals 35 | if one_animal.number_of_legs == number_of_legs] 36 | 37 | def number_of_legs(self): 38 | """Return the total number of legs of all animals""" 39 | return sum(one_animal.number_of_legs 40 | for one_cage in self.cages 41 | for one_animal in one_cage.animals) 42 | -------------------------------------------------------------------------------- /ch09-objects/e45b1_any_colors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 45, beyond 1: any_colors""" 4 | 5 | 6 | class Animal(): 7 | def __init__(self, color, number_of_legs): 8 | self.species = self.__class__.__name__ 9 | self.color = color 10 | self.number_of_legs = number_of_legs 11 | 12 | def __repr__(self): 13 | return f'{self.color} {self.species}, {self.number_of_legs} legs' 14 | 15 | 16 | class Wolf(Animal): 17 | 18 | def __init__(self, color): 19 | super().__init__(color, 4) 20 | 21 | 22 | class Sheep(Animal): 23 | 24 | def __init__(self, color): 25 | super().__init__(color, 4) 26 | 27 | 28 | class Snake(Animal): 29 | 30 | def __init__(self, color): 31 | super().__init__(color, 0) 32 | 33 | 34 | class Parrot(Animal): 35 | 36 | def __init__(self, color): 37 | super().__init__(color, 2) 38 | 39 | 40 | class Cage(): 41 | 42 | def __init__(self, id_number): 43 | self.id_number = id_number 44 | self.animals = [] 45 | 46 | def add_animals(self, *animals): 47 | for one_animal in animals: 48 | self.animals.append(one_animal) 49 | 50 | def __repr__(self): 51 | output = f'Cage {self.id_number}\n' 52 | output += '\n'.join('\t' + str(animal) 53 | for animal in self.animals) 54 | return output 55 | 56 | 57 | class NoColorsPassedError(Exception): 58 | pass 59 | 60 | 61 | class Zoo(): 62 | 63 | def __init__(self): 64 | self.cages = [] 65 | 66 | def add_cages(self, *cages): 67 | for one_cage in cages: 68 | self.cages.append(one_cage) 69 | 70 | def __repr__(self): 71 | return '\n'.join(str(one_cage) 72 | for one_cage in self.cages) 73 | 74 | def animals_by_color(self, *args): 75 | if not args: 76 | raise NoColorsPassedError 77 | 78 | return [one_animal 79 | for one_cage in self.cages 80 | for one_animal in one_cage.animals 81 | if one_animal.color in args] 82 | 83 | def animals_by_legs(self, number_of_legs): 84 | return [one_animal 85 | for one_cage in self.cages 86 | for one_animal in one_cage.animals 87 | if one_animal.number_of_legs == number_of_legs] 88 | 89 | def number_of_legs(self): 90 | return sum(one_animal.number_of_legs 91 | for one_cage in self.cages 92 | for one_animal in one_cage.animals) 93 | -------------------------------------------------------------------------------- /ch09-objects/e45b2_transfer_zoo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 45, beyond 3: color_and_legs.py""" 4 | 5 | 6 | class Animal(): 7 | def __init__(self, color, number_of_legs): 8 | self.species = self.__class__.__name__ 9 | self.color = color 10 | self.number_of_legs = number_of_legs 11 | 12 | def __repr__(self): 13 | return f'{self.color} {self.species}, {self.number_of_legs} legs' 14 | 15 | 16 | class Wolf(Animal): 17 | 18 | def __init__(self, color): 19 | super().__init__(color, 4) 20 | 21 | 22 | class Sheep(Animal): 23 | 24 | def __init__(self, color): 25 | super().__init__(color, 4) 26 | 27 | 28 | class Snake(Animal): 29 | 30 | def __init__(self, color): 31 | super().__init__(color, 0) 32 | 33 | 34 | class Parrot(Animal): 35 | 36 | def __init__(self, color): 37 | super().__init__(color, 2) 38 | 39 | 40 | class Cage(): 41 | 42 | def __init__(self, id_number): 43 | self.id_number = id_number 44 | self.animals = [] 45 | 46 | def add_animals(self, *animals): 47 | for one_animal in animals: 48 | self.animals.append(one_animal) 49 | 50 | def __repr__(self): 51 | output = f'Cage {self.id_number}\n' 52 | output += '\n'.join('\t' + str(animal) 53 | for animal in self.animals) 54 | return output 55 | 56 | 57 | class NoColorsPassedError(Exception): 58 | pass 59 | 60 | 61 | class Zoo(): 62 | 63 | def __init__(self): 64 | self.cages = [] 65 | 66 | def add_cages(self, *cages): 67 | for one_cage in cages: 68 | self.cages.append(one_cage) 69 | 70 | def __repr__(self): 71 | return '\n'.join(str(one_cage) 72 | for one_cage in self.cages) 73 | 74 | def animals_by_color(self, color): 75 | return [one_animal 76 | for one_cage in self.cages 77 | for one_animal in one_cage.animals 78 | if one_animal.color == color] 79 | 80 | def animals_by_legs(self, number_of_legs): 81 | return [one_animal 82 | for one_cage in self.cages 83 | for one_animal in one_cage.animals 84 | if one_animal.number_of_legs == number_of_legs] 85 | 86 | def number_of_legs(self): 87 | return sum(one_animal.number_of_legs 88 | for one_cage in self.cages 89 | for one_animal in one_cage.animals) 90 | 91 | def transfer_animal(self, target_zoo, species): 92 | for one_cage in self.cages: 93 | for one_animal in one_cage.animals: 94 | if isinstance(one_animal, species): 95 | one_cage.remove(one_animal) 96 | target_zoo.cages[0].add_animals(one_animal) 97 | -------------------------------------------------------------------------------- /ch09-objects/e45b3_color_and_legs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 9, exercise 45, beyond 3: color_and_legs.py""" 4 | 5 | 6 | class Animal(): 7 | def __init__(self, color, number_of_legs): 8 | self.species = self.__class__.__name__ 9 | self.color = color 10 | self.number_of_legs = number_of_legs 11 | 12 | def __repr__(self): 13 | return f'{self.color} {self.species}, {self.number_of_legs} legs' 14 | 15 | 16 | class Wolf(Animal): 17 | 18 | def __init__(self, color): 19 | super().__init__(color, 4) 20 | 21 | 22 | class Sheep(Animal): 23 | 24 | def __init__(self, color): 25 | super().__init__(color, 4) 26 | 27 | 28 | class Snake(Animal): 29 | 30 | def __init__(self, color): 31 | super().__init__(color, 0) 32 | 33 | 34 | class Parrot(Animal): 35 | 36 | def __init__(self, color): 37 | super().__init__(color, 2) 38 | 39 | 40 | class Cage(): 41 | 42 | def __init__(self, id_number): 43 | self.id_number = id_number 44 | self.animals = [] 45 | 46 | def add_animals(self, *animals): 47 | for one_animal in animals: 48 | self.animals.append(one_animal) 49 | 50 | def __repr__(self): 51 | output = f'Cage {self.id_number}\n' 52 | output += '\n'.join('\t' + str(animal) 53 | for animal in self.animals) 54 | return output 55 | 56 | 57 | class NoColorsPassedError(Exception): 58 | pass 59 | 60 | 61 | class Zoo(): 62 | 63 | def __init__(self): 64 | self.cages = [] 65 | 66 | def add_cages(self, *cages): 67 | for one_cage in cages: 68 | self.cages.append(one_cage) 69 | 70 | def __repr__(self): 71 | return '\n'.join(str(one_cage) 72 | for one_cage in self.cages) 73 | 74 | def animals_by(self, **kwargs): 75 | print(f'{kwargs=}') 76 | return [one_animal 77 | for one_cage in self.cages 78 | for one_animal in one_cage.animals 79 | if (('color' in kwargs and one_animal.color == kwargs['color']) and 80 | ('legs' in kwargs and one_animal.number_of_legs == kwargs['legs']))] 81 | -------------------------------------------------------------------------------- /ch09-objects/test_e38_scoop.py: -------------------------------------------------------------------------------- 1 | from e38_scoop import Scoop, create_scoops 2 | 3 | 4 | def test_scoop(): 5 | s = Scoop('a_flavor') 6 | assert s.flavor == 'a_flavor' 7 | s.flavor = 'abcd' 8 | assert s.flavor == 'abcd' 9 | 10 | 11 | def test_create_scoops(capsys): 12 | create_scoops() 13 | captured_out, captured_err = capsys.readouterr() 14 | assert captured_out == 'chocolate\nvanilla\npersimmon\n' 15 | -------------------------------------------------------------------------------- /ch09-objects/test_e39_bowl.py: -------------------------------------------------------------------------------- 1 | from e39_bowl import Scoop, Bowl 2 | 3 | 4 | def test_basic(): 5 | s1 = Scoop('chocolate') 6 | s2 = Scoop('vanilla') 7 | s3 = Scoop('persimmon') 8 | 9 | b = Bowl() 10 | b.add_scoops(s1, s2) 11 | b.add_scoops(s3) 12 | 13 | assert len(b.scoops) == 3 14 | assert s1 in b.scoops 15 | assert s2 in b.scoops 16 | assert s3 in b.scoops 17 | 18 | assert str(b) == 'chocolate\nvanilla\npersimmon' 19 | -------------------------------------------------------------------------------- /ch09-objects/test_e40_limited_size_bowl.py: -------------------------------------------------------------------------------- 1 | from e40_limited_size_bowl import Scoop, Bowl 2 | 3 | 4 | def test_basic(): 5 | s1 = Scoop('chocolate') 6 | s2 = Scoop('vanilla') 7 | s3 = Scoop('persimmon') 8 | s4 = Scoop('flavor 4') 9 | s5 = Scoop('flavor 5') 10 | 11 | b = Bowl() 12 | b.add_scoops(s1, s2) 13 | b.add_scoops(s3) 14 | b.add_scoops(s4, s5) 15 | 16 | assert len(b.scoops) == 3 17 | assert s1 in b.scoops 18 | assert s2 in b.scoops 19 | assert s3 in b.scoops 20 | 21 | assert str(b) == 'chocolate\nvanilla\npersimmon' 22 | -------------------------------------------------------------------------------- /ch09-objects/test_e41_bigbowl.py: -------------------------------------------------------------------------------- 1 | from e41_bigbowl import Scoop, Bowl, BigBowl 2 | 3 | 4 | def test_basic(): 5 | s1 = Scoop('chocolate') 6 | s2 = Scoop('vanilla') 7 | s3 = Scoop('persimmon') 8 | s4 = Scoop('flavor 4') 9 | s5 = Scoop('flavor 5') 10 | 11 | b = Bowl() 12 | b.add_scoops(s1, s2) 13 | b.add_scoops(s3) 14 | b.add_scoops(s4, s5) 15 | 16 | assert len(b.scoops) == 3 17 | assert s1 in b.scoops 18 | assert s2 in b.scoops 19 | assert s3 in b.scoops 20 | 21 | assert str(b) == 'chocolate\nvanilla\npersimmon' 22 | 23 | 24 | def test_big(): 25 | s1 = Scoop('chocolate') 26 | s2 = Scoop('vanilla') 27 | s3 = Scoop('persimmon') 28 | s4 = Scoop('flavor 4') 29 | s5 = Scoop('flavor 5') 30 | 31 | b = BigBowl() 32 | b.add_scoops(s1, s2) 33 | b.add_scoops(s3) 34 | b.add_scoops(s4, s5) 35 | 36 | assert len(b.scoops) == 5 37 | assert s1 in b.scoops 38 | assert s2 in b.scoops 39 | assert s3 in b.scoops 40 | assert s4 in b.scoops 41 | assert s5 in b.scoops 42 | 43 | assert str(b) == 'chocolate\nvanilla\npersimmon\nflavor 4\nflavor 5' 44 | -------------------------------------------------------------------------------- /ch09-objects/test_e42_flexible_dict.py: -------------------------------------------------------------------------------- 1 | from e42_flexible_dict import FlexibleDict 2 | 3 | 4 | def test_flexible_dict(): 5 | fd = FlexibleDict() 6 | 7 | fd['a'] = 100 8 | assert fd['a'] == 100 9 | 10 | fd[5] = 500 11 | assert fd[5] == 500 12 | 13 | fd[1] = 100 14 | assert fd[1] == 100 15 | assert fd['1'] == 100 16 | 17 | fd['1'] = 100 18 | assert fd[1] == 100 19 | assert fd['1'] == 100 20 | -------------------------------------------------------------------------------- /ch09-objects/test_e43_animals.py: -------------------------------------------------------------------------------- 1 | from e43_animals import Animal, Wolf, Sheep, Snake, Parrot 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('species, color, output', [ 6 | (Wolf, 'black', 'black Wolf, 4 legs'), 7 | (Sheep, 'white', 'white Sheep, 4 legs'), 8 | (Snake, 'brown', 'brown Snake, 0 legs'), 9 | (Parrot, 'green', 'green Parrot, 2 legs') 10 | ]) 11 | def test_animal(species, color, output): 12 | a = species(color) 13 | assert str(a) == output 14 | -------------------------------------------------------------------------------- /ch09-objects/test_e44_cages.py: -------------------------------------------------------------------------------- 1 | from e43_animals import Animal, Wolf, Sheep, Snake, Parrot 2 | from e44_cages import Cage 3 | import pytest 4 | 5 | 6 | def test_cage_creation(): 7 | c = Cage(1) 8 | assert c.id_number == 1 9 | assert c.animals == [] 10 | 11 | 12 | def test_add_animals_to_cages(): 13 | c = Cage(1) 14 | wolf = Wolf('black') 15 | sheep1 = Sheep('black') 16 | sheep2 = Sheep('white') 17 | snake = Snake('brown') 18 | parrot = Parrot('green') 19 | 20 | c.add_animals(wolf, sheep1, sheep2) 21 | assert len(c.animals) == 3 22 | c.add_animals(snake, parrot) 23 | assert len(c.animals) == 5 24 | 25 | assert str(c) == """Cage 1 26 | \tblack Wolf, 4 legs 27 | \tblack Sheep, 4 legs 28 | \twhite Sheep, 4 legs 29 | \tbrown Snake, 0 legs 30 | \tgreen Parrot, 2 legs""" 31 | -------------------------------------------------------------------------------- /ch09-objects/test_e45_zoo.py: -------------------------------------------------------------------------------- 1 | from e43_animals import Animal, Wolf, Sheep, Snake, Parrot 2 | from e44_cages import Cage 3 | from e45_zoo import Zoo 4 | 5 | 6 | def test_zoo(): 7 | 8 | wolf = Wolf('black') 9 | sheep1 = Sheep('black') 10 | sheep2 = Sheep('white') 11 | snake = Snake('brown') 12 | parrot = Parrot('green') 13 | 14 | c1 = Cage(1) 15 | c2 = Cage(2) 16 | 17 | c1.add_animals(wolf, sheep1, sheep2) 18 | c2.add_animals(snake, parrot) 19 | 20 | z = Zoo() 21 | z.add_cages(c1, c2) 22 | assert len(z.cages) == 2 23 | assert z.animals_by_color('black') == [wolf, sheep1] 24 | assert z.animals_by_legs(4) == [wolf, sheep1, sheep2] 25 | assert z.number_of_legs() == 14 26 | 27 | assert str(z) == """Cage 1 28 | \tblack Wolf, 4 legs 29 | \tblack Sheep, 4 legs 30 | \twhite Sheep, 4 legs 31 | Cage 2 32 | \tbrown Snake, 0 legs 33 | \tgreen Parrot, 2 legs""" 34 | -------------------------------------------------------------------------------- /ch10-iterators/e46_myenumerate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 46: myenumerate""" 4 | 5 | 6 | class MyEnumerate(): 7 | """Simple replacement for enumerate""" 8 | 9 | def __init__(self, data): 10 | self.data = data 11 | self.index = 0 12 | 13 | def __iter__(self): 14 | return self 15 | 16 | def __next__(self): 17 | if self.index >= len(self.data): 18 | raise StopIteration 19 | value = (self.index, self.data[self.index]) 20 | self.index += 1 21 | return value 22 | -------------------------------------------------------------------------------- /ch10-iterators/e46b1_enumerate_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 46, beyond 1: enumerate_helper""" 4 | 5 | 6 | class MyEnumerateIterator: 7 | def __init__(self, data): 8 | self.data = data 9 | self.index = 0 10 | 11 | def __next__(self): 12 | if self.index >= len(self.data): 13 | raise StopIteration 14 | value = (self.index, self.data[self.index]) 15 | self.index += 1 16 | return value 17 | 18 | 19 | class MyEnumerate(): 20 | """Simple replacement for enumerate""" 21 | 22 | def __init__(self, data): 23 | self.data = data 24 | 25 | def __iter__(self): 26 | return MyEnumerateIterator(self.data) 27 | -------------------------------------------------------------------------------- /ch10-iterators/e46b2_enumerate_with_default.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 46, beyond 2: enumerate_with_default""" 4 | 5 | 6 | class MyEnumerateIterator: 7 | def __init__(self, data, start): 8 | self.data = data 9 | self.index = start 10 | 11 | def __next__(self): 12 | if self.index >= len(self.data): 13 | raise StopIteration 14 | value = (self.index, self.data[self.index]) 15 | self.index += 1 16 | return value 17 | 18 | 19 | class MyEnumerate(): 20 | """Simple replacement for enumerate""" 21 | 22 | def __init__(self, data, start=0): 23 | self.data = data 24 | self.start = start 25 | 26 | def __iter__(self): 27 | return MyEnumerateIterator(self.data, self.start) 28 | -------------------------------------------------------------------------------- /ch10-iterators/e46b3_enumerate_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 46, beyond 3: enumerate_generator""" 4 | 5 | 6 | def my_enumerate(data, start=0): 7 | index = start 8 | 9 | for one_item in data: 10 | yield (index, one_item) 11 | index += 1 12 | -------------------------------------------------------------------------------- /ch10-iterators/e47_circle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 47: circle""" 4 | 5 | 6 | class CircleIterator(): 7 | """Iterator for Circle.""" 8 | 9 | def __init__(self, data, max_times): 10 | self.data = data 11 | self.max_times = max_times 12 | self.index = 0 13 | 14 | def __next__(self): 15 | if self.index >= self.max_times: 16 | raise StopIteration 17 | value = self.data[self.index % len(self.data)] 18 | self.index += 1 19 | return value 20 | 21 | 22 | class Circle(): 23 | """Class that produces an iterator, which repeatedly cycles 24 | through the elements of an iterator until returning max_times 25 | items. """ 26 | 27 | def __init__(self, data, max_times): 28 | self.data = data 29 | self.max_times = max_times 30 | 31 | def __iter__(self): 32 | return CircleIterator(self.data, self.max_times) 33 | -------------------------------------------------------------------------------- /ch10-iterators/e47b1_circle_inherit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 47, beyond 1: circle_inherit""" 4 | 5 | 6 | class CircleIterator(): 7 | def __init__(self, data, max_times): 8 | self.data = data 9 | self.max_times = max_times 10 | self.index = 0 11 | 12 | def __next__(self): 13 | if self.index >= self.max_times: 14 | raise StopIteration 15 | 16 | iterated_data = getattr(self, self.returns) 17 | 18 | value = iterated_data[self.index % len(iterated_data)] 19 | self.index += 1 20 | return value 21 | 22 | def __iter__(self): 23 | return type(self)(self.data, self.max_times) 24 | 25 | 26 | class Circle(CircleIterator): 27 | def __init__(self, data, max_times): 28 | super().__init__(data, max_times) 29 | self.returns = 'data' 30 | -------------------------------------------------------------------------------- /ch10-iterators/e47b2_circle_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 47, beyond 2: circle_generator""" 4 | 5 | 6 | def circle(data, max_times): 7 | 8 | for index in range(max_times): 9 | yield data[index % len(data)] 10 | -------------------------------------------------------------------------------- /ch10-iterators/e47b3_myrange.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 47, beyond 3: myrange""" 4 | 5 | 6 | class MyRange: 7 | def __init__(self, first, second=None, step=1): 8 | if second is None: 9 | self.current = 0 10 | self.stop = first 11 | else: 12 | self.current = first 13 | self.stop = second 14 | self.step = step 15 | 16 | def __iter__(self): 17 | return self 18 | 19 | def __next__(self): 20 | if self.current >= self.stop: 21 | raise StopIteration 22 | 23 | value = self.current 24 | self.current += self.step 25 | return value 26 | -------------------------------------------------------------------------------- /ch10-iterators/e48_all_lines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 48: all_lines""" 4 | 5 | 6 | import os 7 | 8 | 9 | def all_lines(path): 10 | """An iterator that returns, one at a time, each line 11 | from each file in a named directory. 12 | 13 | Any file that cannot be opened, for whatever reason, is ignored. 14 | """ 15 | for filename in os.listdir(path): 16 | full_filename = os.path.join(path, filename) 17 | try: 18 | for line in open(full_filename): 19 | yield line 20 | except OSError: 21 | pass 22 | -------------------------------------------------------------------------------- /ch10-iterators/e48b1_all_lines_tuple.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 48, beyond 1: all_lines_tuple""" 4 | 5 | import os 6 | 7 | 8 | def all_lines_tuple(path): 9 | for file_index, filename in enumerate(os.listdir(path)): 10 | full_filename = os.path.join(path, filename) 11 | try: 12 | for line_index, line in enumerate(open(full_filename)): 13 | 14 | yield (full_filename, file_index, line_index, line) 15 | except OSError: 16 | pass 17 | -------------------------------------------------------------------------------- /ch10-iterators/e48b2_all_lines_alt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 48, beyond 2: all_lines_alt""" 4 | 5 | import os 6 | 7 | 8 | def open_file_safely(filename): 9 | try: 10 | return open(filename) 11 | except OSError: 12 | return None 13 | 14 | 15 | def all_lines_alt(path): 16 | all_files = [open_file_safely(os.path.join(path, filename)) 17 | for filename in os.listdir(path)] 18 | 19 | while all_files: 20 | for one_file in all_files: 21 | if one_file is None: 22 | all_files.remove(one_file) 23 | continue 24 | 25 | one_line = one_file.readline() 26 | 27 | if one_line: 28 | yield one_line 29 | else: 30 | all_files.remove(one_file) 31 | -------------------------------------------------------------------------------- /ch10-iterators/e48b3_all_lines_matching.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 48, beyond 3: all_lines_matching""" 4 | 5 | import os 6 | 7 | 8 | def all_lines(path, s): 9 | for filename in os.listdir(path): 10 | full_filename = os.path.join(path, filename) 11 | try: 12 | for line in open(full_filename): 13 | if s in line: 14 | yield line 15 | except OSError: 16 | pass 17 | -------------------------------------------------------------------------------- /ch10-iterators/e49_elapsed_since.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 49: elapsed_since""" 4 | 5 | import time 6 | 7 | 8 | def elapsed_since(data): 9 | """A generator that takes an iterable as input. 10 | 11 | With each iteration, it yields a tuple containing the 12 | data and the time since the previous iteration. 13 | """ 14 | last_time = None 15 | for item in data: 16 | current_time = time.perf_counter() 17 | delta = current_time - (last_time or current_time) 18 | last_time = time.perf_counter() 19 | yield (delta, item) 20 | -------------------------------------------------------------------------------- /ch10-iterators/e49b1_elapsed_since_wait.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 49, beyond 1: elapsed_since_wait""" 4 | 5 | import time 6 | 7 | 8 | def elapsed_since(data, min_wait): 9 | last_time = None 10 | for item in data: 11 | current_time = time.perf_counter() 12 | delta = current_time - (last_time or current_time) 13 | 14 | if delta < min_wait: 15 | time.sleep(min_wait - delta) 16 | 17 | last_time = time.perf_counter() 18 | yield (delta, item) 19 | -------------------------------------------------------------------------------- /ch10-iterators/e49b2_file_usage_timing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 49, beyond 2: file_usage_timing""" 4 | 5 | import os 6 | 7 | 8 | def file_usage_timing(dirname): 9 | for one_filename in os.listdir(dirname): 10 | full_filename = os.path.join(dirname, one_filename) 11 | 12 | yield (full_filename, 13 | os.stat(full_filename).st_mtime, 14 | os.stat(full_filename).st_ctime, 15 | os.stat(full_filename).st_atime) 16 | -------------------------------------------------------------------------------- /ch10-iterators/e49b3_yield_filter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 49, beyond 3: yield_filter""" 4 | 5 | 6 | def yield_filter(data, func): 7 | for one_item in data: 8 | if func(one_item): 9 | yield one_item 10 | -------------------------------------------------------------------------------- /ch10-iterators/e50_mychain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 50: mychain""" 4 | 5 | 6 | def mychain(*args): 7 | """Generator that takes any number of iterables 8 | as arguments. It yields, one at a time, each of the 9 | elements of each iterable. 10 | 11 | It is similar to itertools.chain. 12 | """ 13 | for arg in args: 14 | for item in arg: 15 | yield item 16 | -------------------------------------------------------------------------------- /ch10-iterators/e50b1_zip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 50, beyond 1: myzip""" 4 | 5 | 6 | def myzip(*args): 7 | for i in range(len(min(args, key=len))): 8 | yield tuple(one_arg[i] 9 | for one_arg in args) 10 | -------------------------------------------------------------------------------- /ch10-iterators/e50b2_all_lines_mychain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 50, beyond 2: all_lines_mychain""" 4 | 5 | 6 | def mychain(*args): 7 | for arg in args: 8 | for item in arg: 9 | yield item 10 | 11 | 12 | def all_lines(path): 13 | return mychain(*(open(os.path.join(path, filename)) 14 | for filename in os.listdir(path) 15 | if os.path.isfile(os.path.join(path, filename)))) 16 | -------------------------------------------------------------------------------- /ch10-iterators/e50b3_myrange_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Solution to chapter 10, exercise 50, beyond 3: myrange_generator""" 4 | 5 | 6 | def myrange(first, second=None, step=1): 7 | if second is None: 8 | current = 0 9 | stop = first 10 | 11 | else: 12 | current = first 13 | stop = second 14 | 15 | while current < stop: 16 | yield current 17 | current += step 18 | -------------------------------------------------------------------------------- /ch10-iterators/test_e46_myenumerate.py: -------------------------------------------------------------------------------- 1 | from e46_myenumerate import MyEnumerate 2 | 3 | 4 | def test_simple(): 5 | m = MyEnumerate('abc') 6 | assert list(m) == [(0, 'a'), (1, 'b'), (2, 'c')] 7 | 8 | 9 | def test_is_iterable(): 10 | m = MyEnumerate('abc') 11 | assert hasattr(m, '__iter__') 12 | -------------------------------------------------------------------------------- /ch10-iterators/test_e47_circle.py: -------------------------------------------------------------------------------- 1 | from e47_circle import Circle 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize('iterable, maxtimes, output', [ 6 | ('abcd', 7, 'abcdabc'), 7 | ([10, 20, 30], 2, [10, 20]), 8 | ([10, 20, 30], 8, [10, 20, 30, 10, 20, 30, 10, 20]), 9 | ]) 10 | def test_circle(iterable, maxtimes, output): 11 | assert list(Circle(iterable, maxtimes)) == list(output) 12 | -------------------------------------------------------------------------------- /ch10-iterators/test_e48_all_lines.py: -------------------------------------------------------------------------------- 1 | from e48_all_lines import all_lines 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def small_file(tmp_path): 7 | f = tmp_path / 'smallfile.txt' 8 | f.write_text('''This is the first line 9 | and this is the second line 10 | and this is, to no one's surprise, the third line 11 | but the biggest word will probably be encyclopedia''') 12 | return f 13 | 14 | 15 | @pytest.fixture 16 | def big_file(tmp_path): 17 | f = tmp_path / 'bigfile.txt' 18 | f.write_text('''This is the first line of a big file 19 | 20 | and this is the second line 21 | and this is, to no one's surprise, the third line 22 | but the biggest word will probably be encyclopedia''') 23 | return f 24 | 25 | 26 | def test_iterator(tmp_path): 27 | g = all_lines(tmp_path) 28 | assert iter(g) == g 29 | 30 | 31 | def test_empty(tmp_path): 32 | lines = list(all_lines(tmp_path)) 33 | assert len(lines) == 0 34 | 35 | 36 | def test_simple(tmp_path, small_file, big_file): 37 | lines = list(all_lines(tmp_path)) 38 | assert len(lines) == 9 39 | assert lines[0] == 'This is the first line\n' 40 | assert lines[-1] == 'but the biggest word will probably be encyclopedia' 41 | -------------------------------------------------------------------------------- /ch10-iterators/test_e49_elapsed_since.py: -------------------------------------------------------------------------------- 1 | from e49_elapsed_since import elapsed_since 2 | import time 3 | 4 | 5 | def test_simple(): 6 | for index, t in enumerate(elapsed_since('abc')): 7 | assert isinstance(t, tuple) 8 | assert isinstance(t[0], float) 9 | assert isinstance(t[1], str) 10 | 11 | if index == 0: 12 | assert t[0] == 0 13 | 14 | else: 15 | assert int(t[0]) == 1 16 | 17 | time.sleep(1) 18 | -------------------------------------------------------------------------------- /ch10-iterators/test_e50_mychain.py: -------------------------------------------------------------------------------- 1 | from e50_mychain import mychain 2 | 3 | 4 | def test_empty(): 5 | assert list(mychain()) == [] 6 | 7 | 8 | def test_some(): 9 | assert list(mychain('abc', [10, 20, 30])) == ['a', 'b', 'c', 10, 20, 30] 10 | -------------------------------------------------------------------------------- /files/books.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reuven/python-workout/b9c68520d572bf70eff8e554a8ee9c8702c88e6e/files/books.zip -------------------------------------------------------------------------------- /files/linux-etc-passwd.txt: -------------------------------------------------------------------------------- 1 | # This is a comment 2 | # You should ignore me 3 | root:x:0:0:root:/root:/bin/bash 4 | daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin 5 | bin:x:2:2:bin:/bin:/usr/sbin/nologin 6 | sys:x:3:3:sys:/dev:/usr/sbin/nologin 7 | sync:x:4:65534:sync:/bin:/bin/sync 8 | games:x:5:60:games:/usr/games:/usr/sbin/nologin 9 | man:x:6:12:man:/var/cache/man:/usr/sbin/nologin 10 | lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin 11 | mail:x:8:8:mail:/var/mail:/usr/sbin/nologin 12 | 13 | 14 | 15 | news:x:9:9:news:/var/spool/news:/usr/sbin/nologin 16 | uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin 17 | proxy:x:13:13:proxy:/bin:/usr/sbin/nologin 18 | www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin 19 | backup:x:34:34:backup:/var/backups:/usr/sbin/nologin 20 | list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin 21 | irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin 22 | gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin 23 | 24 | nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin 25 | syslog:x:101:104::/home/syslog:/bin/false 26 | messagebus:x:102:106::/var/run/dbus:/bin/false 27 | landscape:x:103:109::/var/lib/landscape:/bin/false 28 | jci:x:955:955::/home/jci:/bin/bash 29 | sshd:x:104:65534::/var/run/sshd:/usr/sbin/nologin 30 | user:x:1000:1000:user,,,:/home/user:/bin/bash 31 | reuven:x:1001:1001:Reuven M. Lerner,,,:/home/reuven:/bin/bash 32 | postfix:x:105:113::/var/spool/postfix:/bin/false 33 | colord:x:106:116:colord colour management daemon,,,:/var/lib/colord:/bin/false 34 | postgres:x:107:117:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash 35 | dovecot:x:108:119:Dovecot mail server,,,:/usr/lib/dovecot:/bin/false 36 | dovenull:x:109:120:Dovecot login user,,,:/nonexistent:/bin/false 37 | postgrey:x:110:121::/var/lib/postgrey:/bin/false 38 | debian-spamd:x:111:122::/var/lib/spamassassin:/bin/sh 39 | memcache:x:113:124:Memcached,,,:/nonexistent:/bin/false 40 | genadi:x:1002:1003:Genadi Reznichenko,,,:/home/genadi:/bin/bash 41 | shira:x:1003:1004:Shira Friedman,,,:/home/shira:/bin/bash 42 | atara:x:1004:1005:Atara Lerner-Friedman,,,:/home/atara:/bin/bash 43 | shikma:x:1005:1006:Shikma Lerner-Friedman,,,:/home/shikma:/bin/bash 44 | amotz:x:1006:1007:Amotz Lerner-Friedman,,,:/home/amotz:/bin/bash 45 | mysql:x:114:125:MySQL Server,,,:/nonexistent:/bin/false 46 | clamav:x:115:126::/var/lib/clamav:/bin/false 47 | amavis:x:116:127:AMaViS system user,,,:/var/lib/amavis:/bin/sh 48 | opendkim:x:117:128::/var/run/opendkim:/bin/false 49 | gitlab-redis:x:999:1009::/var/opt/gitlab/redis:/bin/nologin 50 | gitlab-psql:x:998:1010::/var/opt/gitlab/postgresql:/bin/sh 51 | git:x:1007:1008:GitLab,,,:/home/git:/bin/bash 52 | opendmarc:x:118:129::/var/run/opendmarc:/bin/false 53 | dkim-milter-python:x:119:130::/var/run/dkim-milter-python:/bin/false 54 | deploy:x:1008:1011:Deploy,,,:/home/deploy:/bin/bash 55 | redis:x:112:123:redis server,,,:/var/lib/redis:/bin/false 56 | -------------------------------------------------------------------------------- /files/nums.txt: -------------------------------------------------------------------------------- 1 | 5 2 | 10 3 | 20 4 | 3 5 | 20 6 | 7 | 25 8 | -------------------------------------------------------------------------------- /files/output.txt: -------------------------------------------------------------------------------- 1 | elif gib a fo enil tsrif eht si sihT 2 | 3 | enil dnoces eht si siht dna 4 | enil driht eht ,esirprus s'eno on ot ,si siht dna 5 | aidepolcycne eb ylbaborp lliw drow tseggib eht tub 6 | -------------------------------------------------------------------------------- /files/shoe-data.txt: -------------------------------------------------------------------------------- 1 | Adidas orange 43 2 | Nike black 41 3 | Adidas black 39 4 | New Balance pink 41 5 | Nike white 44 6 | New Balance orange 38 7 | Nike pink 44 8 | Adidas pink 44 9 | New Balance orange 39 10 | New Balance black 43 11 | New Balance orange 44 12 | Nike black 41 13 | Adidas orange 37 14 | Adidas black 38 15 | Adidas pink 41 16 | Adidas white 36 17 | Adidas orange 36 18 | Nike pink 41 19 | Adidas pink 35 20 | New Balance orange 37 21 | Nike pink 43 22 | Nike black 43 23 | Nike black 42 24 | Nike black 35 25 | Adidas black 41 26 | New Balance pink 40 27 | Adidas white 35 28 | New Balance pink 41 29 | New Balance orange 41 30 | Adidas orange 40 31 | New Balance orange 40 32 | New Balance white 44 33 | New Balance pink 40 34 | Nike black 43 35 | Nike pink 36 36 | New Balance white 39 37 | Nike black 42 38 | Adidas black 41 39 | New Balance orange 40 40 | New Balance black 40 41 | Nike white 37 42 | Adidas black 39 43 | Adidas black 40 44 | Adidas orange 38 45 | New Balance orange 39 46 | Nike black 35 47 | Adidas white 39 48 | Nike white 37 49 | Adidas orange 37 50 | Adidas pink 35 51 | New Balance orange 41 52 | Nike pink 44 53 | Nike pink 38 54 | Adidas black 39 55 | New Balance white 35 56 | Nike pink 40 57 | Nike white 44 58 | Nike orange 38 59 | Adidas orange 42 60 | New Balance orange 43 61 | Adidas pink 39 62 | Adidas pink 41 63 | Adidas pink 39 64 | Nike white 37 65 | Nike orange 38 66 | Adidas orange 39 67 | Nike pink 40 68 | Adidas white 36 69 | Nike orange 40 70 | New Balance pink 40 71 | New Balance black 40 72 | New Balance pink 40 73 | Adidas pink 41 74 | Nike pink 40 75 | Nike black 41 76 | Nike black 39 77 | New Balance white 38 78 | Adidas black 41 79 | Nike orange 36 80 | Nike black 38 81 | New Balance black 40 82 | New Balance pink 40 83 | Adidas black 42 84 | Adidas white 40 85 | New Balance orange 38 86 | Nike pink 41 87 | Adidas orange 37 88 | Nike black 44 89 | Adidas pink 36 90 | Adidas white 35 91 | Nike black 38 92 | Nike pink 42 93 | New Balance black 43 94 | Nike white 38 95 | New Balance pink 39 96 | Nike orange 39 97 | New Balance orange 40 98 | New Balance white 44 99 | Adidas black 42 100 | Nike black 35 101 | -------------------------------------------------------------------------------- /files/wcfile.txt: -------------------------------------------------------------------------------- 1 | This is a test file. 2 | 3 | It contains 28 words and 20 different words. 4 | 5 | It also contains 165 characters. 6 | 7 | It also contains 11 lines. 8 | 9 | It is also self-referential. 10 | 11 | Wow! 12 | -------------------------------------------------------------------------------- /output.csv: -------------------------------------------------------------------------------- 1 | root 0 2 | daemon 1 3 | bin 2 4 | sys 3 5 | sync 4 6 | games 5 7 | man 6 8 | lp 7 9 | mail 8 10 | atara 1004 11 | shikma 1005 12 | amotz 1006 13 | -------------------------------------------------------------------------------- /output.txt: -------------------------------------------------------------------------------- 1 | elif gib a fo enil tsrif eht si sihT 2 | 3 | enil dnoces eht si siht dna 4 | enil driht eht ,esirprus s'eno on ot ,si siht dna 5 | aidepolcycne eb ylbaborp lliw drow tseggib eht tub 6 | --------------------------------------------------------------------------------