diff --git a/.gitignore b/.gitignore index 68ca39690..35db47f33 100644 --- a/.gitignore +++ b/.gitignore @@ -11,23 +11,3 @@ tex2pdf* .coverage .idea .vscode -02_crowsnest/crowsnest.py -03_picnic/picnic.py -04_jump_the_five/jump.py -05_howler/howler.py -06_wc/wc.py -07_gashlycrumb/gashlycrumb.py -08_apples_and_bananas/apples.py -09_abuse/abuse.py -10_telephone/telephone.py -11_bottles_of_beer/bottles.py -12_ransom/ransom.py -13_twelve_days/twelve_days.py -14_rhymer/rhymer.py -15_kentucky_friar/friar.py -16_scrambler/scrambler.py -17_mad_libs/mad.py -18_gematria/gematria.py -19_wod/wod.py -20_password/password.py -21_tictactoe/tictactoe.py diff --git a/01_hello/Makefile b/01_hello/Makefile index 1cbd24b42..37577a3d0 100644 --- a/01_hello/Makefile +++ b/01_hello/Makefile @@ -2,3 +2,6 @@ test: pytest -xv test.py + +lint: + pylint hello.py diff --git a/01_hello/hello.py b/01_hello/hello.py new file mode 100755 index 000000000..e52da0a24 --- /dev/null +++ b/01_hello/hello.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" +Author: Johan Runesson +Purpose: Say hello +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get the command-line arguments""" + parser = argparse.ArgumentParser(description='Say hello') + parser.add_argument('-n', + '--name', + metavar='name', + default='World', + help='Name to greet') + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Start things up""" + args = get_args() + + print('Hello, ' + args.name + '!') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/02_crowsnest/crowsnest.py b/02_crowsnest/crowsnest.py new file mode 100755 index 000000000..58e0dabd3 --- /dev/null +++ b/02_crowsnest/crowsnest.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +""" +Author : Johan Runesson +Date : 2020-08-03 +Purpose: Crow\'s Nest -- choose the correct article +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Crow\'s Nest -- choose the correct article', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('word', metavar='word', help='A word') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Start things up""" + + args = get_args() + word = args.word + article = 'an' if word[0].lower() in 'aeiou' else 'a' + + print(f'Ahoy, Captain, {article} {word} off the larboard bow!') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/03_picnic/picnic.py b/03_picnic/picnic.py new file mode 100755 index 000000000..9f8ac5ef9 --- /dev/null +++ b/03_picnic/picnic.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +""" +Author : Johan Runesson +Date : 2020-08-03 +Purpose: Picnic game +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Picnic game', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('item', + metavar='item', + nargs='+', + help='Item(s) to bring') + + parser.add_argument('-s', + '--sorted', + action='store_true', + help='Sort the items') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Start things up""" + + args = get_args() + items = args.item + sorted = args.sorted + num = len(items) + + if sorted: + items.sort() + + if num > 1: + items.insert(-1, 'and') + + if num > 3: + last = items.pop(); + items = ', '.join(items) + ' ' + last + else: + items = ' '.join(items) + + print(f'You are bringing {items}.') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/04_jump_the_five/jump.py b/04_jump_the_five/jump.py new file mode 100755 index 000000000..9f41d9db6 --- /dev/null +++ b/04_jump_the_five/jump.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +""" +Author : Johan Runesson +Date : 2020-08-03 +Purpose: Jump the Five +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Jump the Five', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('number', metavar='str', help='A number') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Start things up""" + + args = get_args() + number = args.number + + jumper = { + '1': '9', + '2': '8', + '3': '7', + '4': '6', + '5': '0', + '6': '4', + '7': '3', + '8': '2', + '9': '1', + '0': '5' + } + + for char in number: + print(jumper.get(char, char), end='') + print() # To get rid of the %-character at the end (not sure why it gets printed) + + # print(number.translate(number.maketrans(jumper))) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/05_howler/README.md b/05_howler/README.md index 36bb0a24d..e69de29bb 100644 --- a/05_howler/README.md +++ b/05_howler/README.md @@ -1,73 +0,0 @@ -# Howler - -https://www.youtube.com/playlist?list=PLhOuww6rJJNNzo5zqtx0388myQkUKyrQz - -Write a program that uppercases the given text: - -``` -$ ./howler.py 'The quick brown fox jumps over the lazy dog.' -THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. -``` - -If the text names a file, uppercase the contents of the file: - -``` -$ ./howler.py ../inputs/fox.txt -THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. -``` - -If given no arguments, print a brief usage: - -``` -$ ./howler.py -usage: howler.py [-h] [-o str] str -howler.py: error: the following arguments are required: str -``` - -If the `-o` or `--outfile` option is present, write the output to the given file and print nothing: - -``` -$ ./howler.py ../inputs/fox.txt -o out.txt -``` - -There should now be an `out.txt` file with the contents: - -``` -$ cat out.txt -THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. -``` - -Respond to `-h` or `--help` with a longer usage: - -``` -$ ./howler.py -h -usage: howler.py [-h] [-o str] str - -Howler (upper-cases input) - -positional arguments: - str Input string or file - -optional arguments: - -h, --help show this help message and exit - -o str, --outfile str - Output filename (default: ) -``` - -Run the test suite to ensure your program is working correctly: - -``` -$ make test -pytest -xv test.py -============================= test session starts ============================== -... -collected 5 items - -test.py::test_exists PASSED [ 20%] -test.py::test_usage PASSED [ 40%] -test.py::test_text_stdout PASSED [ 60%] -test.py::test_text_outfile PASSED [ 80%] -test.py::test_file PASSED [100%] - -============================== 5 passed in 0.40s =============================== -``` diff --git a/05_howler/hagrid.txt b/05_howler/hagrid.txt new file mode 100644 index 000000000..e69de29bb diff --git a/05_howler/howler.py b/05_howler/howler.py new file mode 100755 index 000000000..b2924ecb7 --- /dev/null +++ b/05_howler/howler.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +""" +Author : Johan Runesson +Date : 2020-08-04 +Purpose: Howler (upper-cases input) +""" + +import argparse +import os + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Howler (upper-cases input)', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', + metavar='text', + type=str, + help='Input string or file') + + parser.add_argument('-o', + '--outfile', + help='Output filename', + metavar='str', + type=str, + default='') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Start things up""" + + args = get_args() + text = args.text + outfile = args.outfile + output = text.upper() + + # validate arguments inside get_args (see solution1.py) + if os.path.isfile(text): + fh_in = open(text) + output = fh_in.read().upper() + fh_in.close() + + if outfile: + fh_out = open(outfile, 'wt') + fh_out.write(output) + fh_out.close() + else: + print(output) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/05_howler/test.txt b/05_howler/test.txt new file mode 100644 index 000000000..05b387ba1 --- /dev/null +++ b/05_howler/test.txt @@ -0,0 +1 @@ +hej hej he diff --git a/05_howler/test2.txt b/05_howler/test2.txt new file mode 100644 index 000000000..71f115934 --- /dev/null +++ b/05_howler/test2.txt @@ -0,0 +1 @@ +HEJ HEJ HE diff --git a/06_wc/wc.py b/06_wc/wc.py new file mode 100755 index 000000000..c3b0f6a70 --- /dev/null +++ b/06_wc/wc.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Emulate wc (word count)""" + +import argparse +import sys + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Emulate wc (word count)', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('file', + metavar='FILE', + nargs='*', + default=[sys.stdin], + type=argparse.FileType('rt'), + help='Input file(s)') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Start things up""" + + args = get_args() + + total_lines, total_bytes, total_words = 0, 0, 0 + for fh in args.file: + num_lines, num_words, num_bytes = 0, 0, 0 + for line in fh: + num_lines += 1 + num_bytes += len(line) + num_words += len(line.split()) + + total_lines += num_lines + total_bytes += num_bytes + total_words += num_words + + print(f'{num_lines:8}{num_words:8}{num_bytes:8} {fh.name}') + + if len(args.file) > 1: + print(f'{total_lines:8}{total_words:8}{total_bytes:8} total') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/07_gashlycrumb/gashlycrumb.py b/07_gashlycrumb/gashlycrumb.py new file mode 100755 index 000000000..4646aaa51 --- /dev/null +++ b/07_gashlycrumb/gashlycrumb.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +""" +Author : Johan Runesson +Date : 2020-08-04 +Purpose: Gashlycrumb +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Gashlycrumb', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('letter', + metavar='letter', + nargs='+', + help='Letter(s)') + + parser.add_argument('-f', + '--file', + help='Input file', + metavar='FILE', + type=argparse.FileType('rt'), + default='gashlycrumb.txt') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Start things up""" + + args = get_args() + dictionary = { line[0].lower(): line.rstrip() for line in args.file } + # dictionary = {} + + # for line in args.file: + # dictionary[line[0].lower()] = line.rstrip() + + for letter in args.letter: + print(dictionary.get(letter.lower(), f'I do not know "{letter}".')) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/08_apples_and_bananas/apples.py b/08_apples_and_bananas/apples.py new file mode 100755 index 000000000..06cf9ac52 --- /dev/null +++ b/08_apples_and_bananas/apples.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +""" +Author : Johan Runesson +Date : 2020-08-04 +Purpose: Apples and bananas +""" + +import argparse +import os + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Apples and bananas', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', metavar='text', help='Input text or file') + + parser.add_argument('-v', + '--vowel', + help='The vowel to substitute', + metavar='vowel', + type=str, + choices=list('aeiou'), + default='a') + + args = parser.parse_args() + + if os.path.isfile(args.text): + args.text = open(args.text).read().rstrip() + + return args + + +# -------------------------------------------------- +def main(): + """Start things up""" + + args = get_args() + text = args.text + vowel = args.vowel + + new_text = text.translate(str.maketrans('aeiouAEIOU', vowel * 5 + vowel.upper() * 5)); + + print(new_text) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/09_abuse/abuse.py b/09_abuse/abuse.py new file mode 100755 index 000000000..0796d3184 --- /dev/null +++ b/09_abuse/abuse.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +""" +Author : Johan Runesson +Date : 2020-08-04 +Purpose: Heap abuse +""" + +import argparse +import random + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Heap abuse', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-a', + '--adjectives', + help='Number of adjectives', + metavar='adjectives', + type=int, + default=2) + + parser.add_argument('-n', + '--number', + help='Number of insults', + metavar='insults', + type=int, + default=3) + + parser.add_argument('-s', + '--seed', + help='Random seed', + metavar='seed', + type=int, + default=None) + + args = parser.parse_args() + + if args.adjectives < 1: + parser.error(f'--adjectives "{args.adjectives}" must be > 0') + + if args.number < 1: + parser.error(f'--number "{args.number}" must be > 0') + + return args + + +# -------------------------------------------------- +def main(): + """Start things up""" + + args = get_args() + random.seed(args.seed) + + adjectives = ''' + bankrupt base caterwauling corrupt cullionly + detestable dishonest false filthsome filthy + foolish foul gross heedless indistinguishable + infected insatiate irksome lascivious lecherous + loathsome lubbery old peevish rascaly rotten + ruinous scurilous scurvy slanderous sodden-witted + thin-faced toad-spotted unmannered vile wall-eyed + '''.split() + + nouns = ''' + Judas Satan ape ass barbermonger beggar block boy + braggart butt carbuncle coward coxcomb cur dandy + degenerate fiend fishmonger fool gull harpy jack + jolthead knave liar lunatic maw milksop minion + ratcatcher recreant rogue scold slave swine traitor + varlet villain worm + '''.split() + + for _ in range(args.number): + adjective = ', '.join(random.sample(adjectives, k=args.adjectives)) + print(f'You {adjective} {random.choice(nouns)}!') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/10_telephone/telephone.py b/10_telephone/telephone.py new file mode 100755 index 000000000..5f769df3f --- /dev/null +++ b/10_telephone/telephone.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +""" +Author : Johan Runesson +Date : 2020-08-07 +Purpose: Telephone +""" + +import argparse +import os +import random +import string + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Telephone', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', metavar='text', help='Input text or file') + + parser.add_argument('-s', + '--seed', + help='Random seed', + metavar='int', + type=int, + default=None) + + parser.add_argument('-m', + '--mutations', + help='Percent mutations', + metavar='int', + type=float, + default=0.1) + + args = parser.parse_args() + + if args.mutations < 0 or args.mutations > 1: + parser.error(f'--mutations "{args.mutations}" must be between 0 and 1') + + if os.path.isfile(args.text): + args.text = open(args.text).read().rstrip() + + return args + + +# -------------------------------------------------- +def main(): + """Start things up""" + + args = get_args() + text = args.text + random.seed(args.seed) + alpha = ''.join(sorted(string.ascii_letters + string.punctuation)) + num_mutations = round(len(text) * args.mutations) + indexes = random.sample(range(len(text)), num_mutations) + + for i in indexes: + new_char = random.choice(alpha.replace(text[i], '')) + text = text[:i] + new_char + text[i+1:] + # alpha2 = alpha.replace(text[i], '') + # text = text[:i] + random.choice(alpha2) + text[i+1:] + + + print(f'You said: "{args.text}"') + print(f'I heard : "{text}"') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 000000000..807a2b99f --- /dev/null +++ b/NOTES.md @@ -0,0 +1,116 @@ +# Notes + +## Links + +- https://docs.python.org/3/ + +## Tips + +- The nargs (number of arguments) option to argparse allows you to validate the number of arguments from the user. The asterisk ('*') means zero or more, whereas '+' means one or more. +- If you define an argument using type=argparse.FileType('rt'), argparse will validate that the user has provided a readable text file and will make the value available in your code as an open file handle. +- You can read and write from the standard in/out file handles by using sys.stdin and sys.stdout. + +## Tools + +Run tests: + +``` +pytest -xv test.py +``` + +Run lint: + +``` +pylint hello.py +``` + +## Tricks + +Make executable: + +``` +chmod +x hello.py +``` + +Add to PATH: + +``` +mkdir ~/bin +cp 01_hello/hello.py ~/bin +PATH=~/bin:$PATH +``` + +Get type of variable: + +``` +type(word) +``` + +Get character of string: + +``` +word = "hej" +word[0] # h +word[2] # j +word[-1] # j +word[:2] # he +``` + +Get length of string: + +``` +len('hej') # 3 +``` + +Get help in repl: + +```bash +python3 +help(str) +``` + +Read a file (reads the entire file into memory): + +``` +fh = open(text) +text = fh.read() +fh.close() +``` + +Read a file (without reading the entire file into memory): + +``` +fh = open(text) +for line in fh: + print(line.upper()) +fh.close() +``` + +Handle text that might be a file of a string: + +``` +if os.path.isfile(args.text): + args.text = open(args.text) +else: + args.text = io.StringIO(args.text + '\n') +``` + +Handle output to file if specified else to print: + +``` +out_fh = open(args.outfile, 'wt') if args.outfile else sys.stdout +``` + +Create a dictionary with a loop: + +``` +lookup = { line[0].upper(): line.rstrip() for line in fh } +``` + +Change characters in a string (non-deterministic) (good for speed and when working with large files: +``` +new_text = '' +for char in args.text: + new_text += random.choice(alpha) if random.random() <= args.mutations else char +print(new_text) +``` diff --git a/out.txt b/out.txt new file mode 100644 index 000000000..1746bd0e2 --- /dev/null +++ b/out.txt @@ -0,0 +1,2 @@ +this is some text +this is some more text