├── .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}{tagname}>'
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 |
--------------------------------------------------------------------------------