diff --git a/Exception_Handling_and_Debugging.md b/Exception_Handling_and_Debugging.md index 0ae2379..be784d1 100644 --- a/Exception_Handling_and_Debugging.md +++ b/Exception_Handling_and_Debugging.md @@ -37,7 +37,7 @@ while True: print("Square of entered number is: {}".format(usr_num * usr_num)) ``` -* `except` can be used for particular error (in this case `ValueError`) or without argument to handle any kind of error +* `except` can be used for particular error (in this case `ValueError`) ``` $ ./user_input_exception.py @@ -51,9 +51,10 @@ Square of entered number is: 9 **Further Reading** -* [Python docs - errors and exception handling](https://docs.python.org/3/tutorial/errors.html) -* [Python docs - raising exceptions](https://docs.python.org/3/tutorial/errors.html#raising-exceptions) +* [Python docs - errors, exception handling and raising exceptions](https://docs.python.org/3/tutorial/errors.html) * [Python docs - built-in exceptions](https://docs.python.org/3/library/exceptions.html#bltin-exceptions) +* [stackoverflow - exception message capturing](https://stackoverflow.com/questions/4690600/python-exception-message-capturing) +* [stackoverflow - avoid bare exceptions](https://stackoverflow.com/questions/14797375/should-i-always-specify-an-exception-type-in-except-statements) * [Python docs - pass statement](https://docs.python.org/3/reference/simple_stmts.html#grammar-token-pass_stmt)
diff --git a/Exercises.md b/Exercises.md index 3699732..727358f 100644 --- a/Exercises.md +++ b/Exercises.md @@ -12,6 +12,8 @@ For some questions, Python program with assert statements is provided to automatically test your solution in the [exercise_files](https://github.com/learnbyexample/Python_Basics/tree/master/exercise_files) directory - for ex: [Q2a - length of integer numbers](https://github.com/learnbyexample/Python_Basics/blob/master/exercise_files/q2a_int_length.py). The directory also has sample input text files. +You can also solve these exercises on [repl.it](https://repl.it/community/classrooms/52626), with an option to submit them for review. +
## 1) Variables and Print @@ -49,10 +51,7 @@ College : PSG Tech # bonus: handle -ve numbers and check for input type >>> len_int(-42) 2 ->>> len_int('a') -Traceback (most recent call last): - File "", line 1, in - File "", line 3, in len_int +# len_int('a') should give TypeError: provide only integer input ``` @@ -76,21 +75,61 @@ False True >>> str_anagram('beat', 'table') False +>>> str_anagram('Tap', 'paT') +True >>> str_anagram('beat', 'abet') True ``` +**Q2d)** Returns corresponding integer or floating-point number (See [Number and String data types](./Number_and_String_datatypes.md) chapter for details) + +```python +# number input +>>> num(3) +3 +>>> num(0x1f) +31 +>>> num(3.32) +3.32 + +# string input +>>> num('123') +123 +>>> num('-78') +-78 +>>> num(" 42 \n ") +42 +>>> num('3.14') +3.14 +>>> num('3.982e5') +398200.0 + +>>> s = '56' +>>> num(s) + 44 +100 +``` + +Other than integer or floating, only string data type should be accepted. Also, provide custom error message if input cannot be converted + +```python +# num(['1', '2.3']) +TypeError: not a valid input + +# num('foo') +ValueError: could not convert string to int or float +``` +
## 3) Control structures **Q3a)** Write a function that returns -* 'Good' for numbers divisable by 7 -* 'Food' for numbers divisable by 6 -* 'Universe' for numbers divisable by 42 +* 'Good' for numbers divisible by 7 +* 'Food' for numbers divisible by 6 +* 'Universe' for numbers divisible by 42 * 'Oops' for all other numbers -* Only one output, divisable by 42 takes precedence +* Only one output, divisible by 42 takes precedence ```python >>> six_by_seven(66) @@ -218,6 +257,24 @@ IndexError: list index out of range 'd' ``` +**Q4c)** Write a function that accepts a string input and returns slices + +* if input string is less than 3 characters long, return a list with input string as the only element +* otherwise, return list with all string slices greater than 1 character long +* order of slices should be same as shown in examples below + +```python +>>> word_slices('i') +['i'] +>>> word_slices('to') +['to'] + +>>> word_slices('are') +['ar', 'are', 're'] +>>> word_slices('table') +['ta', 'tab', 'tabl', 'table', 'ab', 'abl', 'able', 'bl', 'ble', 'le'] +``` +
## 5) File @@ -251,6 +308,38 @@ $ ./extract_sum.py 2298 ``` +**Q5c)** Sort file contents in alphabetic order based on each line's extension + +* extension here is defined as the string after the last `.` in the line +* if line doesn't have a `.`, those lines should come before lines with `.` +* sorting should be case-insensitive +* use rest of string as tie-breaker if there are more than one line with same extension +* assume input file is ASCII encoded and small enough to fit in memory + +*bonus*: instead of printing results to stdout, change the input file itself with sorted result + +```bash +$ cat f3.txt +power.Log +foo.123.txt +list +report_12.log +baz.TXT +hello.RB +loop.do.rb +Fav_books + +$ ./sort_by_ext.py +Fav_books +list +power.Log +report_12.log +hello.RB +loop.do.rb +baz.TXT +foo.123.txt +``` +
## 6) Text processing @@ -314,6 +403,36 @@ True False ``` +**Q6c)** Find the maximum nested depth of curly braces + +Unbalanced or wrongly ordered braces should return `-1` + +Iterating over input string is one way to solve this, another is to use regular expressions + +```python +>>> max_nested_braces('a*b') +0 +>>> max_nested_braces('{a+2}*{b+c}') +1 +>>> max_nested_braces('{{a+2}*{{b+{c*d}}+e*d}}') +4 +>>> max_nested_braces('a*b+{}') +1 +>>> max_nested_braces('}a+b{') +-1 +>>> max_nested_braces('a*b{') +-1 +``` + +*bonus*: empty braces, i.e `{}` should return `-1` + +```python +>>> max_nested_braces('a*b+{}') +-1 +>>> max_nested_braces('a*{b+{}+c*{e*3.14}}') +-1 +``` +
## 7) Misc @@ -322,6 +441,22 @@ False **Q7b)** Open a browser along with any link, for ex: https://github.com/learnbyexample/Python_Basics (**hint** use `webbrowser` module) +**Q7c)** Write a function that + +* accepts a filesystem path(default) or a url(indicated by True as second argument) +* returns the longest word(here word is defined as one or more consecutive sequence of alphabets of either case) +* assume that input encoding is **utf-8** and small enough to fit in memory and that there's only one distinct longest word + +```python +>>> ip_path = 'poem.txt' +>>> longest_word(ip_path) +'Violets' + +>>> ip_path = 'https://www.gutenberg.org/files/60/60.txt' +>>> longest_word(ip_path, True) +'misunderstandings' +``` +

diff --git a/Functions.md b/Functions.md index d6a834d..22486dd 100644 --- a/Functions.md +++ b/Functions.md @@ -233,6 +233,8 @@ Error!! Not a valid input >>> op_fmt = '{} + {} = {}' >>> op_fmt.format(num1, num2, num1 + num2) '42 + 7 = 49' +>>> op_fmt.format(num1, 29, num1 + 29) +'42 + 29 = 71' # and of course the expression can be used inside print directly >>> print('{} + {} = {}'.format(num1, num2, num1 + num2)) @@ -306,7 +308,32 @@ Error!! Not a valid input 42 ``` +* similar to the `r` raw string prefix, using `f` prefix allows to represent format strings + * introduced in Python v3.6 +* similar to `str.format()`, the variables/expressions are specified within `{}` + +```python +>>> num1 = 42 +>>> num2 = 7 +>>> f'{num1} + {num2} = {num1 + num2}' +'42 + 7 = 49' +>>> print(f'{num1} + {num2} = {num1 + num2}') +42 + 7 = 49 + +>>> appx_pi = 22 / 7 +>>> f'{appx_pi:08.3f}' +'0003.143' + +>>> f'{20:x}' +'14' +>>> f'{20:#x}' +'0x14' +``` + +**Further Reading** + * [Python docs - formatstrings](https://docs.python.org/3/library/string.html#formatstrings) - for more info and examples +* [Python docs - f-strings](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) - for more examples and caveats
diff --git a/Lists.md b/Lists.md index c436fe3..143acb6 100644 --- a/Lists.md +++ b/Lists.md @@ -301,10 +301,14 @@ IndexError: list index out of range * using [del](https://docs.python.org/3/reference/simple_stmts.html#del) to delete elements ```python ->>> books = ['Harry Potter', 'Sherlock Holmes', 'To Kill a Mocking Bird'] ->>> del books[1] ->>> books -['Harry Potter', 'To Kill a Mocking Bird'] +>>> nums = [1.2, -0.2, 0, 2, 4, 23] +>>> del nums[1] +>>> nums +[1.2, 0, 2, 4, 23] +# can use slicing notation as well +>>> del nums[1:4] +>>> nums +[1.2, 23] >>> list_2D = [[1, 3, 2, 10], [1.2, -0.2, 0, 2]] >>> del list_2D[0][1] diff --git a/Number_and_String_datatypes.md b/Number_and_String_datatypes.md index 136ac7a..c6596d5 100644 --- a/Number_and_String_datatypes.md +++ b/Number_and_String_datatypes.md @@ -87,6 +87,23 @@ Variable data type is automatically determined by Python. They only need to be a 25 ``` +* `_` can be used between digits for readability + * introduced in Python v3.6 + +```python +>>> 1_000_000 +1000000 +>>> 1_00.3_352 +100.3352 +>>> 0xff_ab1 +1047217 + +# f-strings formatting explained in a later chapter +>>> num = 34 ** 32 +>>> print(f'{num:_}') +10_170_102_859_315_411_774_579_628_461_341_138_023_025_901_305_856 +``` + **Further Reading** * [Python docs - numbers](https://docs.python.org/3/tutorial/introduction.html#numbers) @@ -162,6 +179,11 @@ Hello World >>> word = 'buffalo ' >>> print(word * 8) buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo + +# Python v3.6 allows variable interpolation with f-strings +>>> msg = f'{str1} there' +>>> msg +'Hello there' ``` * Triple quoted strings @@ -201,6 +223,7 @@ $ **Further Reading** * [Python docs - strings](https://docs.python.org/3/tutorial/introduction.html#strings) +* [Python docs - f-strings](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) - for more examples and caveats * [Python docs - List of Escape Sequences and more info on strings](https://docs.python.org/3/reference/lexical_analysis.html#strings) * [Python docs - Binary Sequence Types](https://docs.python.org/3/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview) * [formatting triple quoted strings](https://stackoverflow.com/questions/3877623/in-python-can-you-have-variables-within-triple-quotes-if-so-how) @@ -268,5 +291,3 @@ True * [Python docs - Numeric types](https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex) - complete list of operations and precedence * [Python docs - String methods](https://docs.python.org/3/library/stdtypes.html#string-methods) - - diff --git a/README.md b/README.md index 95c70df..a52652f 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,25 @@ -Use [![Join the chat at https://gitter.im/learnbyexample/scripting_course](https://badges.gitter.im/learnbyexample/scripting_course.svg)](https://gitter.im/learnbyexample/scripting_course) if you need help, have suggestions, etc -
+--- + +:warning: :warning: I'm archiving this repo, as I don't intend to work on this repo further. + +I'm re-using materials in this repo for the **100 Page Python Intro** book (https://github.com/learnbyexample/100_page_python_intro). + +I'm also working on **Practice Python Projects** book (https://github.com/learnbyexample/practice_python_projects), which I had intended in this repo for the `mini_projects` folder. + +--- + +


+ # Python Basics Introduction to Python - Syntax, working with Shell commands, Files, Text Processing, and more... * Suitable for a one/two day workshop for Python beginners -* [Python curated resources](https://github.com/learnbyexample/scripting_course/blob/master/Python_curated_resources.md) for more complete resources list, including tutorials for beginners -* For more related resources, visit [scripting course](https://github.com/learnbyexample/scripting_course) +* Visit [Python re(gex)?](https://github.com/learnbyexample/py_regular_expressions) repo for a book on regular expressions +* [Python resources for everybody](https://learnbyexample.github.io/py_resources/) for a curated and searchable collection, including resources for complete beginners to programming +* For more related resources, visit [scripting course](https://github.com/learnbyexample/scripting_course) and my programming blog https://learnbyexample.github.io
@@ -48,10 +59,19 @@ Introduction to Python - Syntax, working with Shell commands, Files, Text Proces
+## Contributing + +* Please open an issue for typos/bugs/suggestions/etc + * As this repo is no longer actively worked upon, **please do not submit pull requests** +* Share the repo with friends/colleagues, on social media, etc to help reach other learners +* In case you need to reach me, mail me at `echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode` or send a DM via [twitter](https://twitter.com/learn_byexample) + +
+ # ebook * Read as ebook on [gitbook](https://learnbyexample.gitbooks.io/python-basics/content/index.html) -* Download ebook for offline reading - [link](https://www.gitbook.com/book/learnbyexample/python-basics/details) +* All `legacy.gitbook.com` links are now automatically redirected to `gitbook.com`, so there's no longer an option to download ebooks for offline reading
@@ -59,6 +79,7 @@ Introduction to Python - Syntax, working with Shell commands, Files, Text Proces * [automatetheboringstuff](https://automatetheboringstuff.com/) for getting me started with Python * [/r/learnpython/](https://www.reddit.com/r/learnpython/) - helpful forum for beginners and experienced programmers alike +* [stackoverflow](https://stackoverflow.com/) - for getting answers to pertinent questions as well as sharpening skills by understanding and answering questions * [Devs and Hackers](http://devup.in/) - helpful slack group * [Weekly Coders, Hackers & All Tech related thread](https://www.reddit.com/r/india/search?q=Weekly+Coders%2C+Hackers+%26+All+Tech+related+thread+author%3Aavinassh&restrict_sr=on&sort=new&t=all) - for suggestions and critique @@ -67,3 +88,4 @@ Introduction to Python - Syntax, working with Shell commands, Files, Text Proces # License This work is licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/) + diff --git a/Sequence_Set_Dict_data_types.md b/Sequence_Set_Dict_data_types.md index 4efa0db..8b5cba8 100644 --- a/Sequence_Set_Dict_data_types.md +++ b/Sequence_Set_Dict_data_types.md @@ -5,8 +5,8 @@ * [Set](#set) * [Dictionary](#dictionary) -We have already seen Sequence types in previous chapters - strings, ranges and lists -We'll see some more operations on strings followed by Tuple, Set and Dict types in this chapter +We have already seen Sequence types in previous chapters - strings, ranges and lists. Tuple is another sequence type +We'll see some more operations on strings followed by Tuple, Set and Dict in this chapter
@@ -100,7 +100,8 @@ True ### Tuples -* Tuples can be thought of as sort of lists but immutable +* Tuples are similar to lists but immutable and useful in other ways too +* Individual elements can be both mutable/immutable ```python >>> north_dishes = ('Aloo tikki', 'Baati', 'Khichdi', 'Makki roti', 'Poha') @@ -163,6 +164,15 @@ Poha >>> b 5 +>>> c = 'foo' +>>> a, b, c = c, a, b +>>> a +'foo' +>>> b +20 +>>> c +5 + >>> def min_max(arr): ... return min(arr), max(arr) ... @@ -212,6 +222,7 @@ Poha ``` * [Python docs - tuple](https://docs.python.org/3/library/stdtypes.html#tuple) +* [Python docs - tuple tutorial](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences)
@@ -294,6 +305,7 @@ True ### Dictionary * `dict` types can be thought of as unordered list of `key:value` pairs or a named list of items +* up to Python v3.5 (and some implementations of v3.6) do not retain order of insertion of dict elements ```python >>> marks = {'Rahul' : 86, 'Ravi' : 92, 'Rohit' : 75} @@ -380,7 +392,35 @@ False Try the 'East' speciality 'rosgulla' today ``` +* From Python v3.7 onwards, dict implementation will retain insertion order + * some implementations like the reference CPython implementation for v3.6 also retains the insertion order + +```python +>>> marks = {'Rahul' : 86, 'Ravi' : 92, 'Rohit' : 75, 'Rajan': 79} +>>> marks +{'Rahul': 86, 'Ravi': 92, 'Rohit': 75, 'Rajan': 79} + +>>> for name, mark in marks.items(): +... print(f'{name:5s}: {mark}') +... +Rahul: 86 +Ravi : 92 +Rohit: 75 +Rajan: 79 + +>>> del marks['Ravi'] +>>> marks +{'Rahul': 86, 'Rohit': 75, 'Rajan': 79} + +>>> marks['Ranjit'] = 65 +>>> marks +{'Rahul': 86, 'Rohit': 75, 'Rajan': 79, 'Ranjit': 65} +``` + **Further Reading** * [Python docs - dict](https://docs.python.org/3/library/stdtypes.html#dict) * [Python docs - pprint](https://docs.python.org/3/library/pprint.html) +* [detailed tutorial on dict](http://www.sharats.me/posts/the-python-dictionary/) +* [Using dict to eliminate duplicates while retaining order](https://twitter.com/raymondh/status/944125570534621185) + diff --git a/Text_Processing.md b/Text_Processing.md index f89031a..9033677 100644 --- a/Text_Processing.md +++ b/Text_Processing.md @@ -226,144 +226,206 @@ False ### Regular Expressions -* Handy reference of regular expression elements +* Handy reference of regular expression (RE) elements | Meta characters | Description | | ------------- | ----------- | -| ^ | anchor, match from beginning of string | -| $ | anchor, match end of string | -| . | Match any character except newline character \n | +| `\A` | anchor to restrict matching to beginning of string | +| `\Z` | anchor to restrict matching to end of string | +| `^` | anchor to restrict matching to beginning of line | +| `$` | anchor to restrict matching to end of line | +| `.` | Match any character except newline character `\n` | | | | OR operator for matching multiple patterns | -| () | for grouping patterns and also extraction | -| [] | Character class - match one character among many | -| \^ | use \ to match meta characters like ^ | +| `(RE)` | capturing group | +| `(?:RE)` | non-capturing group | +| `[]` | Character class - match one character among many | +| `\^` | prefix `\` to literally match meta characters like `^` |
-| Quantifiers | Description | +| Greedy Quantifiers | Description | | ------------- | ----------- | -| * | Match zero or more times the preceding character | -| + | Match one or more times the preceding character | -| ? | Match zero or one times the preceding character | -| {n} | Match exactly n times | -| {n,} | Match at least n times | -| {n,m} | Match at least n times but not more than m times | +| `*` | Match zero or more times | +| `+` | Match one or more times | +| `?` | Match zero or one times | +| `{m,n}` | Match `m` to `n` times (inclusive) | +| `{m,}` | Match at least m times | +| `{,n}` | Match up to `n` times (including `0` times) | +| `{n}` | Match exactly n times | + +Appending a `?` to greedy quantifiers makes them non-greedy
| Character classes | Description | | ------------- | ----------- | -| [aeiou] | Match any vowel | -| \[^aeiou] | ^ inverts selection, so this matches any consonant | -| [a-f] | Match any of abcdef character | -| \d | Match a digit, same as [0-9] | -| \D | Match non-digit, same as \[^0-9] or \[^\d] | -| \w | Match alphanumeric and underscore character, same as [a-zA-Z_] | -| \W | Match non-alphanumeric and underscore character, same as \[^a-zA-Z_] or \[^\w] | -| \s | Match white-space character, same as [\ \t\n\r\f\v] | -| \S | Match non white-space character, same as \[^\s] | -| \b | word boundary, word defined as sequence of alphanumeric characters | -| \B | not a word boundary | +| `[aeiou]` | Match any vowel | +| `[^aeiou]` | `^` inverts selection, so this matches any consonant | +| `[a-f]` | `-` defines a range, so this matches any of abcdef characters | +| `\d` | Match a digit, same as `[0-9]` | +| `\D` | Match non-digit, same as `[^0-9]` or `[^\d]` | +| `\w` | Match alphanumeric and underscore character, same as `[a-zA-Z0-9_]` | +| `\W` | Match non-alphanumeric and underscore character, same as `[^a-zA-Z0-9_]` or `[^\w]` | +| `\s` | Match white-space character, same as `[\ \t\n\r\f\v]` | +| `\S` | Match non white-space character, same as `[^\s]` | +| `\b` | word boundary, see `\w` for characters constituting a word | +| `\B` | not a word boundary |
-| Compilation Flags | Description | +| Flags | Description | | ------------- | ----------- | -| re.I | ignore case | -| re.M | multiline mode, ^ and $ anchors work on internal lines | -| re.S | singleline mode, . will also match \n | -| re.V | verbose mode, for better readability and adding comments | +| `re.I` | Ignore case | +| `re.M` | Multiline mode, `^` and `$` anchors work on lines | +| `re.S` | Singleline mode, `.` will also match `\n` | +| `re.X` | Verbose mode, for better readability and adding comments | -* [Python docs - Compilation Flags](https://docs.python.org/3/howto/regex.html#compilation-flags) - for more details and long names for flags +See [Python docs - Compilation Flags](https://docs.python.org/3/howto/regex.html#compilation-flags) for more details and long names for flags
| Variable | Description | | ------------- | ----------- | -| \1, \2, \3 etc | backreferencing matched patterns | -| \g<1>, \g<2>, \g<3> etc | backreferencing matched patterns, useful to differentiate numbers and backreferencing | +| `\1`, `\2`, `\3` ... `\99` | backreferencing matched patterns | +| `\g<1>`, `\g<2>`, `\g<3>` ... | backreferencing matched patterns, prevents ambiguity | +| `\g<0>` | entire matched portion | + +`\0` and `\100` onwards are considered as octal values, hence cannot be used as backreference.
### Pattern matching and extraction -* matching/extracting sequence of characters -* use `re.search()` to see if a string contains a pattern or not -* use `re.findall()` to get a list of matching patterns -* use `re.split()` to get a list from splitting a string based on a pattern -* their syntax given below +To match/extract sequence of characters, use + +* `re.search()` to see if input string contains a pattern or not +* `re.findall()` to get a list of all matching portions +* `re.finditer()` to get an iterator of `re.Match` objects of all matching portions +* `re.split()` to get a list from splitting input string based on a pattern + +Their syntax is as follows: ```python re.search(pattern, string, flags=0) re.findall(pattern, string, flags=0) +re.finditer(pattern, string, flags=0) re.split(pattern, string, maxsplit=0, flags=0) ``` +* As a good practice, always use **raw strings** to construct RE, unless other formats are required + * this will avoid clash of backslash escaping between RE and normal quoted strings +* examples for `re.search` + ```python ->>> import re ->>> string = "This is a sample string" +>>> sentence = 'This is a sample string' ->>> bool(re.search('is', string)) +# using normal string methods +>>> 'is' in sentence True - ->>> bool(re.search('this', string)) +>>> 'xyz' in sentence False ->>> bool(re.search('this', string, re.I)) +# need to load the re module before use +>>> import re +# check if 'sentence' contains the pattern described by RE argument +>>> bool(re.search(r'is', sentence)) True - ->>> bool(re.search('T', string)) +>>> bool(re.search(r'this', sentence, flags=re.I)) True +>>> bool(re.search(r'xyz', sentence)) +False +``` ->>> bool(re.search('is a', string)) -True +* examples for `re.findall` ->>> re.findall('i', string) -['i', 'i', 'i'] +```python +# match whole word par with optional s at start and e at end +>>> re.findall(r'\bs?pare?\b', 'par spar apparent spare part pare') +['par', 'spar', 'spare', 'pare'] + +# numbers >= 100 with optional leading zeros +>>> re.findall(r'\b0*[1-9]\d{2,}\b', '0501 035 154 12 26 98234') +['0501', '154', '98234'] + +# if multiple capturing groups are used, each element of output +# will be a tuple of strings of all the capture groups +>>> re.findall(r'(x*):(y*)', 'xx:yyy x: x:yy :y') +[('xx', 'yyy'), ('x', ''), ('x', 'yy'), ('', 'y')] + +# normal capture group will hinder ability to get whole match +# non-capturing group to the rescue +>>> re.findall(r'\b\w*(?:st|in)\b', 'cost akin more east run against') +['cost', 'akin', 'east', 'against'] + +# useful for debugging purposes as well before applying substitution +>>> re.findall(r't.*?a', 'that is quite a fabricated tale') +['tha', 't is quite a', 'ted ta'] ``` -* using regular expressions -* use the `r''` format when using regular expression elements +* examples for `re.split` ```python ->>> string -'This is a sample string' - ->>> re.findall('is', string) -['is', 'is'] +# split based on one or more digit characters +>>> re.split(r'\d+', 'Sample123string42with777numbers') +['Sample', 'string', 'with', 'numbers'] ->>> re.findall('\bis', string) -[] +# split based on digit or whitespace characters +>>> re.split(r'[\d\s]+', '**1\f2\n3star\t7 77\r**') +['**', 'star', '**'] ->>> re.findall(r'\bis', string) -['is'] +# to include the matching delimiter strings as well in the output +>>> re.split(r'(\d+)', 'Sample123string42with777numbers') +['Sample', '123', 'string', '42', 'with', '777', 'numbers'] ->>> re.findall(r'\w+', string) -['This', 'is', 'a', 'sample', 'string'] +# use non-capturing group if capturing is not needed +>>> re.split(r'hand(?:y|ful)', '123handed42handy777handful500') +['123handed42', '777', '500'] +``` ->>> re.split(r'\s+', string) -['This', 'is', 'a', 'sample', 'string'] +* backreferencing ->>> re.split(r'\d+', 'Sample123string54with908numbers') -['Sample', 'string', 'with', 'numbers'] +```python +# whole words that have at least one consecutive repeated character +>>> words = ['effort', 'flee', 'facade', 'oddball', 'rat', 'tool'] ->>> re.split(r'(\d+)', 'Sample123string54with908numbers') -['Sample', '123', 'string', '54', 'with', '908', 'numbers'] +>>> [w for w in words if re.search(r'\b\w*(\w)\1\w*\b', w)] +['effort', 'flee', 'oddball', 'tool'] ``` -* backreferencing +* The `re.search` function returns a `re.Match` object from which various details can be extracted +like the matched portion of string, location of matched portion, etc +* **Note** that output here is shown for Python version **3.7** ```python ->>> quote = "So many books, so little time" +>>> re.search(r'b.*d', 'abc ac adc abbbc') + +# retrieving entire matched portion +>>> re.search(r'b.*d', 'abc ac adc abbbc')[0] +'bc ac ad' + +# capture group example +>>> m = re.search(r'a(.*)d(.*a)', 'abc ac adc abbbc') +# to get matched portion of second capture group +>>> m[2] +'c a' +# to get a tuple of all the capture groups +>>> m.groups() +('bc ac a', 'c a') +``` ->>> re.search(r'([a-z]{2,}).*\1', quote, re.I) -<_sre.SRE_Match object; span=(0, 17), match='So many books, so'> +* examples for `re.finditer` ->>> re.search(r'([a-z])\1', quote, re.I) -<_sre.SRE_Match object; span=(9, 11), match='oo'> +```python +>>> m_iter = re.finditer(r'(x*):(y*)', 'xx:yyy x: x:yy :y') +>>> [(m[1], m[2]) for m in m_iter] +[('xx', 'yyy'), ('x', ''), ('x', 'yy'), ('', 'y')] ->>> re.findall(r'([a-z])\1', quote, re.I) -['o', 't'] +>>> m_iter = re.finditer(r'ab+c', 'abc ac adc abbbc') +>>> for m in m_iter: +... print(m.span()) +... +(0, 3) +(11, 16) ```
@@ -376,55 +438,61 @@ True re.sub(pattern, repl, string, count=0, flags=0) ``` -* simple substitutions -* `re.sub` will not change value of variable passed to it, has to be explicity assigned +* examples +* **Note** that as strings are immutable, `re.sub` will not change value of variable +passed to it, has to be explicity assigned ```python ->>> sentence = 'This is a sample string' ->>> re.sub('sample', 'test', sentence) -'This is a test string' - ->>> sentence -'This is a sample string' ->>> sentence = re.sub('sample', 'test', sentence) ->>> sentence -'This is a test string' - ->>> re.sub('/', '-', '25/06/2016') -'25-06-2016' ->>> re.sub('/', '-', '25/06/2016', count=1) -'25-06/2016' - ->>> greeting = '***** Have a great day *****' ->>> re.sub('\*', '=', greeting) -'===== Have a great day =====' +>>> ip_lines = "catapults\nconcatenate\ncat" +>>> print(re.sub(r'^', r'* ', ip_lines, flags=re.M)) +* catapults +* concatenate +* cat + +# replace 'par' only at start of word +>>> re.sub(r'\bpar', r'X', 'par spar apparent spare part') +'X spar apparent spare Xt' + +# same as: r'part|parrot|parent' +>>> re.sub(r'par(en|ro)?t', r'X', 'par part parrot parent') +'par X X X' + +# remove first two columns where : is delimiter +>>> re.sub(r'\A([^:]+:){2}', r'', 'foo:123:bar:baz', count=1) +'bar:baz' ``` * backreferencing ```python ->>> words = 'night and day' ->>> re.sub(r'(\w+)( \w+ )(\w+)', r'\3\2\1', words) -'day and night' - ->>> line = 'Can you spot the the mistakes? I i seem to not' ->>> re.sub(r'\b(\w+) \1\b', r'\1', line, flags=re.I) -'Can you spot the mistakes? I seem to not' +# remove any number of consecutive duplicate words separated by space +# quantifiers can be applied to backreferences too! +>>> re.sub(r'\b(\w+)( \1)+\b', r'\1', 'aa a a a 42 f_1 f_1 f_13.14') +'aa a 42 f_1 f_13.14' + +# add something around the matched strings +>>> re.sub(r'\d+', r'(\g<0>0)', '52 apples and 31 mangoes') +'(520) apples and (310) mangoes' + +# swap words that are separated by a comma +>>> re.sub(r'(\w+),(\w+)', r'\2,\1', 'a,b 42,24') +'b,a 24,42' ``` * using functions in replace part of `re.sub()` +* **Note** that Python version **3.7** is used here ```python ->>> import math +>>> from math import factorial >>> numbers = '1 2 3 4 5' - >>> def fact_num(n): -... return str(math.factorial(int(n.group(1)))) +... return str(factorial(int(n[0]))) ... ->>> re.sub(r'(\d+)', fact_num, numbers) +>>> re.sub(r'\d+', fact_num, numbers) '1 2 6 24 120' ->>> re.sub(r'(\d+)', lambda m: str(math.factorial(int(m.group(1)))), numbers) +# using lambda +>>> re.sub(r'\d+', lambda m: str(factorial(int(m[0]))), numbers) '1 2 6 24 120' ``` @@ -436,49 +504,45 @@ re.sub(pattern, repl, string, count=0, flags=0) ### Compiling Regular Expressions +* Regular expressions can be compiled using `re.compile` function, which gives back a +`re.Pattern` object +* The top level `re` module functions are all available as methods for this object +* Compiling a regular expression helps if the RE has to be used in multiple +places or called upon multiple times inside a loop (speed benefit) +* By default, Python maintains a small list of recently used RE, so the speed benefit +doesn't apply for trivial use cases + ```python ->>> swap_words = re.compile(r'(\w+)( \w+ )(\w+)') ->>> swap_words -re.compile('(\\w+)( \\w+ )(\\w+)') - ->>> words = 'night and day' - ->>> swap_words.search(words).group() -'night and day' ->>> swap_words.search(words).group(1) -'night' ->>> swap_words.search(words).group(2) -' and ' ->>> swap_words.search(words).group(3) -'day' ->>> swap_words.search(words).group(4) -Traceback (most recent call last): - File "", line 1, in -IndexError: no such group - ->>> bool(swap_words.search(words)) +>>> pet = re.compile(r'dog') +>>> type(pet) + +>>> bool(pet.search('They bought a dog')) True ->>> swap_words.findall(words) -[('night', ' and ', 'day')] +>>> bool(pet.search('A cat crossed their path')) +False ->>> swap_words.sub(r'\3\2\1', words) -'day and night' ->>> swap_words.sub(r'\3\2\1', 'yin and yang') -'yang and yin' +>>> remove_parentheses = re.compile(r'\([^)]*\)') +>>> remove_parentheses.sub('', 'a+b(addition) - foo() + c%d(#modulo)') +'a+b - foo + c%d' +>>> remove_parentheses.sub('', 'Hi there(greeting). Nice day(a(b)') +'Hi there. Nice day' ```
### Further Reading on Regular Expressions +* [Python re(gex)?](https://github.com/learnbyexample/py_regular_expressions) - a book on regular expressions * [Python docs - re module](https://docs.python.org/3/library/re.html) * [Python docs - introductory tutorial to using regular expressions](https://docs.python.org/3/howto/regex.html) -* [developers.google - Regular Expressions tutorial](https://developers.google.com/edu/python/regular-expressions) -* [automatetheboringstuff - Regular Expressions](https://automatetheboringstuff.com/chapter7/) * [Comprehensive reference: What does this regex mean?](https://stackoverflow.com/questions/22937618/reference-what-does-this-regex-mean) +* [rexegg](https://www.rexegg.com/) - tutorials, tricks and more +* [regular-expressions](https://www.regular-expressions.info/) - tutorials and tools +* [CommonRegex](https://github.com/madisonmay/CommonRegex) - collection of common regular expressions * Practice tools - * [online regex tester](https://regex101.com/#python) shows explanations, has reference guides and ability to save and share regex - * [regexone](http://regexone.com/) - interative tutorial + * [regex101](https://regex101.com/) - visual aid and online testing tool for regular expressions, select flavor as Python before use + * [debuggex](https://www.debuggex.com) - railroad diagrams for regular expressions, select flavor as Python before use + * [regexone](https://regexone.com/) - interative tutorial * [cheatsheet](https://www.shortcutfoo.com/app/dojos/python-regex/cheatsheet) - one can also learn it [interactively](https://www.shortcutfoo.com/app/dojos/python-regex) * [regexcrossword](https://regexcrossword.com/) - practice by solving crosswords, read 'How to play' section before you start diff --git a/exercise_files/f3.txt b/exercise_files/f3.txt new file mode 100644 index 0000000..0794f6f --- /dev/null +++ b/exercise_files/f3.txt @@ -0,0 +1,8 @@ +power.Log +foo.123.txt +list +report_12.log +baz.TXT +hello.RB +loop.do.rb +Fav_books diff --git a/exercise_files/poem.txt b/exercise_files/poem.txt new file mode 100644 index 0000000..8eb2255 --- /dev/null +++ b/exercise_files/poem.txt @@ -0,0 +1,4 @@ +Roses are red, +Violets are blue, +Sugar is sweet, +And so are you. diff --git a/exercise_files/q2a_int_length.py b/exercise_files/q2a_int_length.py index 658db59..f8898f4 100755 --- a/exercise_files/q2a_int_length.py +++ b/exercise_files/q2a_int_length.py @@ -11,7 +11,7 @@ def len_int(n): assert len_int(962306349871524124750813401378124) == 33 try: - assert len_int('a') + len_int('a') except TypeError as e: assert str(e) == 'provide only integer input' diff --git a/exercise_files/q2c_str_same_letters.py b/exercise_files/q2c_str_same_letters.py index bbce388..15f3ec4 100755 --- a/exercise_files/q2c_str_same_letters.py +++ b/exercise_files/q2c_str_same_letters.py @@ -5,6 +5,7 @@ def str_anagram(s1, s2): assert str_anagram('god', 'Dog') assert str_anagram('beat', 'abet') +assert str_anagram('Tap', 'paT') assert not str_anagram('beat', 'table') assert not str_anagram('seat', 'teal') diff --git a/exercise_files/q2d_to_num.py b/exercise_files/q2d_to_num.py new file mode 100644 index 0000000..11a270c --- /dev/null +++ b/exercise_files/q2d_to_num.py @@ -0,0 +1,34 @@ +#!/usr/bin/python3 + +def num(ip): + pass + +assert num(3) == 3 +assert num(0x1f) == 31 +assert num(0b101) == 5 +assert num(0o10) == 8 +assert num(3.32) == 3.32 +assert num('123') == 123 +assert num('-78') == -78 +assert num(" 42 \n ") == 42 +assert num('3.14') == 3.14 +assert num('3.982e5') == 398200.0 +s = '56' +assert num(s) + 44 == 100 +s = '8' * 10 +assert num(s) == 8888888888 + +assert type(num('42')) == int +assert type(num('1.23')) == float + +try: + assert num('foo') +except ValueError as e: + assert str(e) == 'could not convert string to int or float' + +try: + assert num(['1', '2.3']) +except TypeError as e: + assert str(e) == 'not a valid input' + +print('all tests passed') diff --git a/exercise_files/q4c_word_slices.py b/exercise_files/q4c_word_slices.py new file mode 100755 index 0000000..562e6e7 --- /dev/null +++ b/exercise_files/q4c_word_slices.py @@ -0,0 +1,13 @@ +#!/usr/bin/python3 + +def word_slices(s): + pass + +assert word_slices('i') == ["i"] +assert word_slices('to') == ["to"] +assert word_slices('are') == ["ar", "are", "re"] +assert word_slices('boat') == ["bo", "boa", "boat", "oa", "oat", "at"] +assert word_slices('table') == ["ta", "tab", "tabl", "table", "ab", + "abl", "able", "bl", "ble", "le"] + +print('all tests passed') diff --git a/exercise_files/q5c_sort_by_ext.py b/exercise_files/q5c_sort_by_ext.py new file mode 100755 index 0000000..125ef2f --- /dev/null +++ b/exercise_files/q5c_sort_by_ext.py @@ -0,0 +1,13 @@ +#!/usr/bin/python3 + +def sort_by_ext(ip): + pass + +exp_op = ['Fav_books\n', 'list\n', 'power.Log\n', + 'report_12.log\n', 'hello.RB\n', 'loop.do.rb\n', + 'baz.TXT\n', 'foo.123.txt\n'] + +assert sort_by_ext('f3.txt') == exp_op + +print('test passed') + diff --git a/exercise_files/q6c_max_nested.py b/exercise_files/q6c_max_nested.py new file mode 100755 index 0000000..13be372 --- /dev/null +++ b/exercise_files/q6c_max_nested.py @@ -0,0 +1,20 @@ +#!/usr/bin/python3 + +def max_nested_braces(expr): + pass + +assert max_nested_braces('a*b') == 0 +assert max_nested_braces('a*b{') == -1 +assert max_nested_braces('a*{b+c}') == 1 +assert max_nested_braces('{a+2}*{b+c}') == 1 +assert max_nested_braces('a*{b+c*{e*3.14}}') == 2 +assert max_nested_braces('a*{b+c*{e*3.14}}}') == -1 +assert max_nested_braces('a*{b+c}}') == -1 +assert max_nested_braces('a*b+{}') == 1 +assert max_nested_braces('}a+b{') == -1 +assert max_nested_braces('{{a+2}*{b+c}+e}') == 2 +assert max_nested_braces('{{a+2}*{b+{c*d}}+e}') == 3 +assert max_nested_braces('{{a+2}*{{b+{c*d}}+e*d}}') == 4 +assert max_nested_braces('{{a+2}*{{b}+{c*d}}+e*d}}') == -1 + +print('all tests passed') diff --git a/exercise_files/q7c_longest_word.py b/exercise_files/q7c_longest_word.py new file mode 100755 index 0000000..24cec7d --- /dev/null +++ b/exercise_files/q7c_longest_word.py @@ -0,0 +1,14 @@ +#!/usr/bin/python3 + +def longest_word(): + pass + +ip_path = 'poem.txt' +assert longest_word(ip_path) == 'Violets' + +# The Scarlet Pimpernel +ip_path = 'https://www.gutenberg.org/files/60/60.txt' +assert longest_word(ip_path, True) == 'misunderstandings' + +print('all tests passed') + diff --git a/exercise_solutions/f3.txt b/exercise_solutions/f3.txt new file mode 100644 index 0000000..0794f6f --- /dev/null +++ b/exercise_solutions/f3.txt @@ -0,0 +1,8 @@ +power.Log +foo.123.txt +list +report_12.log +baz.TXT +hello.RB +loop.do.rb +Fav_books diff --git a/exercise_solutions/poem.txt b/exercise_solutions/poem.txt new file mode 100644 index 0000000..8eb2255 --- /dev/null +++ b/exercise_solutions/poem.txt @@ -0,0 +1,4 @@ +Roses are red, +Violets are blue, +Sugar is sweet, +And so are you. diff --git a/exercise_solutions/q2a_int_length.py b/exercise_solutions/q2a_int_length.py index db7e93b..99a1351 100755 --- a/exercise_solutions/q2a_int_length.py +++ b/exercise_solutions/q2a_int_length.py @@ -15,7 +15,7 @@ def len_int(n): assert len_int(962306349871524124750813401378124) == 33 try: - assert len_int('a') + len_int('a') except TypeError as e: assert str(e) == 'provide only integer input' diff --git a/exercise_solutions/q2c_str_same_letters.py b/exercise_solutions/q2c_str_same_letters.py index b236e54..cbca962 100755 --- a/exercise_solutions/q2c_str_same_letters.py +++ b/exercise_solutions/q2c_str_same_letters.py @@ -5,6 +5,7 @@ def str_anagram(s1, s2): assert str_anagram('god', 'Dog') assert str_anagram('beat', 'abet') +assert str_anagram('Tap', 'paT') assert not str_anagram('beat', 'table') assert not str_anagram('seat', 'teal') diff --git a/exercise_solutions/q2d_to_num.py b/exercise_solutions/q2d_to_num.py new file mode 100755 index 0000000..d840f22 --- /dev/null +++ b/exercise_solutions/q2d_to_num.py @@ -0,0 +1,45 @@ +#!/usr/bin/python3 + +def num(ip): + if type(ip) in (int, float): + return ip + elif type(ip) != str: + raise TypeError('not a valid input') + + try: + return int(ip) + except ValueError: + try: + return float(ip) + except ValueError: + raise ValueError('could not convert string to int or float') + +assert num(3) == 3 +assert num(0x1f) == 31 +assert num(0b101) == 5 +assert num(0o10) == 8 +assert num(3.32) == 3.32 +assert num('123') == 123 +assert num('-78') == -78 +assert num(" 42 \n ") == 42 +assert num('3.14') == 3.14 +assert num('3.982e5') == 398200.0 +s = '56' +assert num(s) + 44 == 100 +s = '8' * 10 +assert num(s) == 8888888888 + +assert type(num('42')) == int +assert type(num('1.23')) == float + +try: + num('foo') +except ValueError as e: + assert str(e) == 'could not convert string to int or float' + +try: + num(['1', '2.3']) +except TypeError as e: + assert str(e) == 'not a valid input' + +print('all tests passed') diff --git a/exercise_solutions/q3a_6by7.py b/exercise_solutions/q3a_6by7.py index 1b993b8..557cd47 100755 --- a/exercise_solutions/q3a_6by7.py +++ b/exercise_solutions/q3a_6by7.py @@ -21,11 +21,4 @@ def six_by_seven(num): ## bonus #for num in range(1, 101): -# if num % 42 == 0: -# print(num, 'Universe') -# elif num % 7 == 0: -# print(num, 'Good') -# elif num % 6 == 0: -# print(num, 'Food') -# else: -# print(num, 'Oops') +# print(num, six_by_seven(num)) diff --git a/exercise_solutions/q4c_word_slices.py b/exercise_solutions/q4c_word_slices.py new file mode 100755 index 0000000..77dae84 --- /dev/null +++ b/exercise_solutions/q4c_word_slices.py @@ -0,0 +1,16 @@ +#!/usr/bin/python3 + +def word_slices(s): + size = len(s) + if size < 3: + return [s] + return [s[i:j+1] for i in range(size-1) for j in range(i+1, size)] + +assert word_slices('i') == ["i"] +assert word_slices('to') == ["to"] +assert word_slices('are') == ["ar", "are", "re"] +assert word_slices('boat') == ["bo", "boa", "boat", "oa", "oat", "at"] +assert word_slices('table') == ["ta", "tab", "tabl", "table", "ab", + "abl", "able", "bl", "ble", "le"] + +print('all tests passed') diff --git a/exercise_solutions/q5a_col_sum.py b/exercise_solutions/q5a_col_sum.py index 13cea40..cefbf05 100755 --- a/exercise_solutions/q5a_col_sum.py +++ b/exercise_solutions/q5a_col_sum.py @@ -3,13 +3,11 @@ with open('f1.txt', 'r', encoding='ascii') as f: total = 0 for line in f: - num = int(line) if type(line) == int else float(line) - #try: - # num = int(line) - #except ValueError: - # num = float(line) - total += num + total += float(line) assert total == 10485.14 print('test passed') + +# for small files that can fit in memory +#total = sum(float(n) for n in open('f1.txt', encoding='ascii').readlines()) diff --git a/exercise_solutions/q5c_sort_by_ext.py b/exercise_solutions/q5c_sort_by_ext.py new file mode 100755 index 0000000..208442c --- /dev/null +++ b/exercise_solutions/q5c_sort_by_ext.py @@ -0,0 +1,14 @@ +#!/usr/bin/python3 + +def sort_by_ext(ip): + lines = open(ip, encoding='ascii').readlines() + return sorted(lines, key=lambda x: (x.split('.')[-1].lower(), x.lower())) + +exp_op = ['Fav_books\n', 'list\n', 'power.Log\n', + 'report_12.log\n', 'hello.RB\n', 'loop.do.rb\n', + 'baz.TXT\n', 'foo.123.txt\n'] + +assert sort_by_ext('f3.txt') == exp_op + +print('test passed') + diff --git a/exercise_solutions/q6c_max_nested.py b/exercise_solutions/q6c_max_nested.py new file mode 100755 index 0000000..b022a79 --- /dev/null +++ b/exercise_solutions/q6c_max_nested.py @@ -0,0 +1,54 @@ +#!/usr/bin/python3 + +def max_nested_braces(expr): + max_count = count = 0 + for char in expr: + if char == '{': + count += 1 + if count > max_count: + max_count = count + elif char == '}': + if count == 0: + return -1 + count -= 1 + + if count != 0: + return -1 + return max_count + +assert max_nested_braces('a*b') == 0 +assert max_nested_braces('a*b{') == -1 +assert max_nested_braces('a*{b+c}') == 1 +assert max_nested_braces('{a+2}*{b+c}') == 1 +assert max_nested_braces('a*{b+c*{e*3.14}}') == 2 +assert max_nested_braces('a*{b+c*{e*3.14}}}') == -1 +assert max_nested_braces('a*{b+c}}') == -1 +assert max_nested_braces('a*b+{}') == 1 +assert max_nested_braces('}a+b{') == -1 +assert max_nested_braces('{{a+2}*{b+c}+e}') == 2 +assert max_nested_braces('{{a+2}*{b+{c*d}}+e}') == 3 +assert max_nested_braces('{{a+2}*{{b+{c*d}}+e*d}}') == 4 +assert max_nested_braces('{{a+2}*{{b}+{c*d}}+e*d}}') == -1 + +print('all tests passed') + + +#### alternate using regular expressions + +#import re +# +#def max_nested_braces(expr): +# count = 0 +# while True: +# expr, no_of_subs = re.subn(r'\{[^{}]*\}', '', expr) +# if no_of_subs == 0: +# break +# count += 1 +# +# if re.search(r'[{}]', expr): +# return -1 +# return count + +# for bonus, use + instead of * +#assert max_nested_braces('a*b+{}') == -1 +#assert max_nested_braces('a*{b+{}+c*{e*3.14}}') == -1 diff --git a/exercise_solutions/q7c_longest_word.py b/exercise_solutions/q7c_longest_word.py new file mode 100755 index 0000000..6286b2e --- /dev/null +++ b/exercise_solutions/q7c_longest_word.py @@ -0,0 +1,21 @@ +#!/usr/bin/python3 + +import urllib.request, re + +def longest_word(ip, url=False): + if url: + ip_data = urllib.request.urlopen(ip).read().decode('utf-8') + else: + ip_data = open(ip, encoding='utf-8').read() + + return sorted(re.findall(r'[a-zA-Z]+', ip_data), key=len)[-1] + +ip_path = 'poem.txt' +assert longest_word(ip_path) == 'Violets' + +# The Scarlet Pimpernel +ip_path = 'https://www.gutenberg.org/files/60/60.txt' +assert longest_word(ip_path, True) == 'misunderstandings' + +print('all tests passed') + diff --git a/mini_projects/pcalc.py b/mini_projects/pcalc.py new file mode 100644 index 0000000..3b0a50b --- /dev/null +++ b/mini_projects/pcalc.py @@ -0,0 +1,55 @@ +#!/usr/bin/python3 + +""" +eval is used, so use at your own risk + +Examples: +$ ./pcalc.py -vx '0b101 + 3' +0b101 + 3 = 0x8 +$ ./pcalc.py '0x23' +35 +$ ./pcalc.py -f2 '76/13' +5.85 +$ ./pcalc.py '27**12' +150094635296999121 +$ echo '97 + 232' | ./pcalc.py +329 + +$ ./pcalc.py '42 + 2s' +Error: Not a valid input expression +""" + +import argparse, sys, fileinput + +parser = argparse.ArgumentParser() +parser.add_argument('arith_expr', nargs='?', default=sys.stdin, help="arithmetic expression") +parser.add_argument('-v', help="verbose, show both input and output in result", action="store_true") +parser.add_argument('-f', type=int, help="specify floating point output precision") +parser.add_argument('-b', help="output in binary format", action="store_true") +parser.add_argument('-o', help="output in octal format", action="store_true") +parser.add_argument('-x', help="output in hexadecimal format", action="store_true") +args = parser.parse_args() + +if type(args.arith_expr) != str: + args.arith_expr = fileinput.input().readline().strip() +ip_expr = args.arith_expr + +try: + result = eval(ip_expr) + + if args.f: + result = "{0:.{1}f}".format(result, args.f) + elif args.b: + result = "{:#b}".format(int(result)) + elif args.o: + result = "{:#o}".format(int(result)) + elif args.x: + result = "{:#x}".format(int(result)) + + if args.v: + print("{} = {}".format(args.arith_expr, result)) + else: + print(result) +except (NameError, SyntaxError) as e: + sys.exit("Error: Not a valid input expression") +