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 [](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")
+