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 0f06e71..727358f 100644
--- a/Exercises.md
+++ b/Exercises.md
@@ -1,18 +1,24 @@
# Exercises
-* [Variables and Print](#variables-and-print)
-* [Functions](#functions)
-* [Control structures](#control-structures)
-* [List](#list)
-* [File](#file)
-* [Text processing](#text-processing)
-* [Misc](#misc)
+1) [Variables and Print](#variables-and-print)
+2) [Functions](#functions)
+3) [Control structures](#control-structures)
+4) [List](#list)
+5) [File](#file)
+6) [Text processing](#text-processing)
+7) [Misc](#misc)
-## Variables and Print
+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.
-Ask user information, for ex: `name`, `department`, `college` etc and display them using print function
+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
+
+**Q1a)** Ask user information, for ex: `name`, `department`, `college` etc and display them using print function
```
# Sample of how program might ask user input and display output afterwards
@@ -30,9 +36,9 @@ College : PSG Tech
-## Functions
+## 2) Functions
-* Returns length of integer numbers
+**Q2a)** Returns length of integer numbers
```python
>>> len_int(962306349871524124750813401378124)
@@ -45,14 +51,11 @@ 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
```
-* Returns True/False - two strings are same irrespective of lowercase/uppercase
+**Q2b)** Returns True/False - two strings are same irrespective of lowercase/uppercase
```python
>>> str_cmp('nice', 'nice')
@@ -65,27 +68,68 @@ True
False
```
-* Returns True/False - two strings are anagrams (assume input consists of alphabets only)
+**Q2c)** Returns True/False - two strings are anagrams (assume input consists of alphabets only)
```python
>>> str_anagram('god', 'Dog')
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
+```
+
-## Control structures
+## 3) Control structures
+
+**Q3a)** Write a function that returns
-* Write a function that returns
- * 'Good' for numbers divisable by 7
- * 'Food' for numbers divisable by 6
- * 'Universe' for numbers divisable by 42
- * 'Oops' for all other numbers
- * Only one output, divisable by 42 takes precedence
+* '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, divisible by 42 takes precedence
```python
>>> six_by_seven(66)
@@ -121,7 +165,7 @@ True
100 Oops
```
-* Print all numbers from 1 to 1000 which reads the same in reversed form in both binary and decimal format
+**Q3b)** Print all numbers from 1 to 1000 which reads the same in reversed form in both binary and decimal format
```
$ ./dec_bin.py
@@ -158,9 +202,9 @@ $ ./dec_bin_oct_hex.py
-## List
+## 4) List
-* Write a function that returns product of all numbers of a list
+**Q4a)** Write a function that returns product of all numbers of a list
```python
>>> product([1, 4, 21])
@@ -172,8 +216,6 @@ $ ./dec_bin_oct_hex.py
*bonus*: works on any kind of iterable
```python
->>> product(b)
-84
>>> product((-3, 11, 2))
-66
>>> product({8, 300})
@@ -185,8 +227,9 @@ $ ./dec_bin_oct_hex.py
# can you identify what mathematical function the last one performs?
```
-* Write a function that returns nth lowest number of a list (or iterable in general)
- * by default, return the lowest if second argument not specified
+**Q4b)** Write a function that returns nth lowest number of a list (or iterable in general). Return the lowest if second argument not specified
+
+*Note* that if a list contains duplicates, they should be handled before determining nth lowest
```python
>>> nums = [42, 23421341, 234.2e3, 21, 232, 12312, -2343]
@@ -205,16 +248,38 @@ IndexError: list index out of range
-2
>>> nth_lowest(nums, 4)
3
+>>> nth_lowest(nums, 5)
+4
>>> nth_lowest('unrecognizable', 3)
'c'
+>>> nth_lowest('abracadabra', 4)
+'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']
```
-## File
+## 5) File
-* Print sum of all numbers from a file containing only single column and all numbers
+**Q5a)** Print sum of all numbers from a file containing only single column and all numbers
```
$ cat f1.txt
@@ -230,7 +295,7 @@ $ ./col_sum.py
10485.14
```
-* Print sum of all numbers (assume only positive integer numbers) from a file containing arbitrary string
+**Q5b)** Print sum of all numbers (assume only positive integer numbers) from a file containing arbitrary string
```
$ cat f2.txt
@@ -243,12 +308,45 @@ $ ./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
+```
+
-## Text processing
+## 6) Text processing
+
+**Q6a)** Check if two words are same or differ by only one character (irrespective of case), input strings should have same length
-* Check if two words are same or differ by only one character (irrespective of case)
- * input strings should have same length
+See also [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance)
```python
>>> is_one_char_diff('bar', 'bar')
@@ -272,8 +370,9 @@ False
False
```
-* Check if a word is in ascending/descending alphabetic order or not (irrespective of case)
- * can you think of a way to do it only using built-in functions and string methods?
+**Q6b)** Check if a word is in ascending/descending alphabetic order or not (irrespective of case)
+
+Can you think of a way to do it only using built-in functions and string methods?
```python
>>> is_alpha_order('bot')
@@ -304,11 +403,62 @@ 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
+```
+
-## Misc
+## 7) Misc
+
+**Q7a)** Play a song (**hint** use `subprocess` module)
+
+**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'
+```
+
+
+
+
-* Play a song
- * **hint** use `subprocess` module
-* Open a browser along with any link, for ex: https://github.com/learnbyexample/Python_Basics
- * **hint** use `webbrowser` module
+For reference solutions, see [exercise_solutions](https://github.com/learnbyexample/Python_Basics/tree/master/exercise_solutions) directory
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/f1.txt b/exercise_files/f1.txt
new file mode 100644
index 0000000..d944f3c
--- /dev/null
+++ b/exercise_files/f1.txt
@@ -0,0 +1,7 @@
+8
+53
+3.14
+84
+73e2
+100
+2937
diff --git a/exercise_files/f2.txt b/exercise_files/f2.txt
new file mode 100644
index 0000000..cc8fa8c
--- /dev/null
+++ b/exercise_files/f2.txt
@@ -0,0 +1,4 @@
+Hello123 World 35
+341 2
+Good 13day
+How are 1784 you
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
new file mode 100755
index 0000000..f8898f4
--- /dev/null
+++ b/exercise_files/q2a_int_length.py
@@ -0,0 +1,18 @@
+#!/usr/bin/python3
+
+def len_int(n):
+ pass
+
+assert len_int(123) == 3
+assert len_int(2) == 1
+assert len_int(+42) == 2
+assert len_int(-42) == 2
+assert len_int(572342) == 6
+assert len_int(962306349871524124750813401378124) == 33
+
+try:
+ len_int('a')
+except TypeError as e:
+ assert str(e) == 'provide only integer input'
+
+print('all tests passed')
diff --git a/exercise_files/q2b_str_comparison.py b/exercise_files/q2b_str_comparison.py
new file mode 100755
index 0000000..f2cf54f
--- /dev/null
+++ b/exercise_files/q2b_str_comparison.py
@@ -0,0 +1,13 @@
+#!/usr/bin/python3
+
+def str_cmp(s1, s2):
+ pass
+
+assert str_cmp('abc', 'Abc')
+assert str_cmp('Hi there', 'hi there')
+assert not str_cmp('foo', 'food')
+assert str_cmp('nice', 'nice')
+assert str_cmp('GoOd DaY', 'gOOd dAy')
+assert not str_cmp('how', 'who')
+
+print('all tests passed')
diff --git a/exercise_files/q2c_str_same_letters.py b/exercise_files/q2c_str_same_letters.py
new file mode 100755
index 0000000..15f3ec4
--- /dev/null
+++ b/exercise_files/q2c_str_same_letters.py
@@ -0,0 +1,12 @@
+#!/usr/bin/python3
+
+def str_anagram(s1, s2):
+ pass
+
+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')
+
+print('all tests passed')
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/q3a_6by7.py b/exercise_files/q3a_6by7.py
new file mode 100755
index 0000000..85b0f40
--- /dev/null
+++ b/exercise_files/q3a_6by7.py
@@ -0,0 +1,13 @@
+#!/usr/bin/python3
+
+def six_by_seven(num):
+ pass
+
+assert six_by_seven(66) == 'Food'
+assert six_by_seven(13) == 'Oops'
+assert six_by_seven(42) == 'Universe'
+assert six_by_seven(14) == 'Good'
+assert six_by_seven(84) == 'Universe'
+assert six_by_seven(235432) == 'Oops'
+
+print('all tests passed')
diff --git a/exercise_files/q4a_iter_product.py b/exercise_files/q4a_iter_product.py
new file mode 100755
index 0000000..867a9df
--- /dev/null
+++ b/exercise_files/q4a_iter_product.py
@@ -0,0 +1,13 @@
+#!/usr/bin/python3
+
+def product(ip_iterable):
+ pass
+
+assert product([1, 4, 21]) == 84
+assert product([-4, 2.3e12, 77.23, 982, 0b101]) == -3.48863356e+18
+assert product((-3, 11, 2)) == -66
+assert product({8, 300}) == 2400
+assert product([234, 121, 23, 945, 0]) == 0
+assert product(range(1, 6)) == 120
+
+print('all tests passed')
diff --git a/exercise_files/q4b_lowest_value.py b/exercise_files/q4b_lowest_value.py
new file mode 100755
index 0000000..7db197c
--- /dev/null
+++ b/exercise_files/q4b_lowest_value.py
@@ -0,0 +1,16 @@
+#!/usr/bin/python3
+
+def nth_lowest():
+ pass
+
+nums = [42, 23421341, 234.2e3, 21, 232, 12312, -2343]
+assert nth_lowest(nums, 3) == 42
+assert nth_lowest(nums, 5) == 12312
+
+nums = [1, -2, 4, 2, 1, 3, 3, 5]
+assert nth_lowest(nums) == -2
+assert nth_lowest(nums, 4) == 3
+
+assert nth_lowest('unrecognizable', 3) == 'c'
+
+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/q6a_one_char_diff.py b/exercise_files/q6a_one_char_diff.py
new file mode 100755
index 0000000..9f84508
--- /dev/null
+++ b/exercise_files/q6a_one_char_diff.py
@@ -0,0 +1,23 @@
+#!/usr/bin/python3
+
+def is_one_char_diff(word1, word2):
+ pass
+
+assert is_one_char_diff('bar', 'car')
+assert is_one_char_diff('bar', 'Bat')
+assert is_one_char_diff('bar', 'bar')
+assert is_one_char_diff('bar', 'baZ')
+assert is_one_char_diff('A', 'b')
+
+assert not is_one_char_diff('a', '')
+assert not is_one_char_diff('bar', 'bark')
+assert not is_one_char_diff('bar', 'Art')
+assert not is_one_char_diff('bar', 'bot')
+assert not is_one_char_diff('ab', '')
+
+assert is_one_char_diff('Food', 'good')
+assert is_one_char_diff('food', 'fold')
+assert not is_one_char_diff('food', 'Foody')
+assert not is_one_char_diff('food', 'fled')
+
+print('all tests passed')
diff --git a/exercise_files/q6b_alpha_order.py b/exercise_files/q6b_alpha_order.py
new file mode 100755
index 0000000..197886b
--- /dev/null
+++ b/exercise_files/q6b_alpha_order.py
@@ -0,0 +1,22 @@
+#!/usr/bin/python3
+
+def is_alpha_order(word):
+ pass
+
+assert is_alpha_order('bot')
+assert is_alpha_order('art')
+assert is_alpha_order('toe')
+assert is_alpha_order('AborT')
+
+assert not is_alpha_order('are')
+assert not is_alpha_order('boat')
+assert not is_alpha_order('Flee')
+
+#sentence
+def is_alpha_order_sentence(sentence):
+ pass
+
+assert is_alpha_order_sentence('Toe got bit')
+assert not is_alpha_order_sentence('All is well')
+
+print('all tests 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/f1.txt b/exercise_solutions/f1.txt
new file mode 100644
index 0000000..d944f3c
--- /dev/null
+++ b/exercise_solutions/f1.txt
@@ -0,0 +1,7 @@
+8
+53
+3.14
+84
+73e2
+100
+2937
diff --git a/exercise_solutions/f2.txt b/exercise_solutions/f2.txt
new file mode 100644
index 0000000..cc8fa8c
--- /dev/null
+++ b/exercise_solutions/f2.txt
@@ -0,0 +1,4 @@
+Hello123 World 35
+341 2
+Good 13day
+How are 1784 you
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/q1a_usr_ip.py b/exercise_solutions/q1a_usr_ip.py
new file mode 100755
index 0000000..267573f
--- /dev/null
+++ b/exercise_solutions/q1a_usr_ip.py
@@ -0,0 +1,25 @@
+#!/usr/bin/python3
+
+print('Please provide the following details')
+name = input('Enter your name: ')
+dept = input('Enter your department: ')
+colg = input('Enter your college: ')
+
+op_fmt = '{:<11}: {}'
+
+print('\n------------------------------------')
+print(op_fmt.format('Name', name))
+print(op_fmt.format('Department', dept))
+print(op_fmt.format('College', colg))
+
+
+####### Alternate
+#print('Please provide the following details')
+#labels = ('Name', 'Department', 'College')
+#usr_details = [input('Enter your ' + itm + ': ') for itm in labels]
+#
+#itm_size = len(sorted(labels, key=len)[-1]) + 1
+#op_fmt = '{:<' + str(itm_size) + '}: {}'
+#print('\n------------------------------------')
+#for k,v in zip(labels, usr_details):
+# print(op_fmt.format(k, v))
diff --git a/exercise_solutions/q2a_int_length.py b/exercise_solutions/q2a_int_length.py
new file mode 100755
index 0000000..99a1351
--- /dev/null
+++ b/exercise_solutions/q2a_int_length.py
@@ -0,0 +1,22 @@
+#!/usr/bin/python3
+
+def len_int(n):
+ if type(n) != int:
+ raise TypeError('provide only integer input')
+
+ str_n = str(abs(n))
+ return len(str_n)
+
+assert len_int(123) == 3
+assert len_int(2) == 1
+assert len_int(+42) == 2
+assert len_int(-42) == 2
+assert len_int(572342) == 6
+assert len_int(962306349871524124750813401378124) == 33
+
+try:
+ len_int('a')
+except TypeError as e:
+ assert str(e) == 'provide only integer input'
+
+print('all tests passed')
diff --git a/exercise_solutions/q2b_str_comparison.py b/exercise_solutions/q2b_str_comparison.py
new file mode 100755
index 0000000..fd8923c
--- /dev/null
+++ b/exercise_solutions/q2b_str_comparison.py
@@ -0,0 +1,13 @@
+#!/usr/bin/python3
+
+def str_cmp(s1, s2):
+ return s1.lower() == s2.lower()
+
+assert str_cmp('abc', 'Abc')
+assert str_cmp('Hi there', 'hi there')
+assert not str_cmp('foo', 'food')
+assert str_cmp('nice', 'nice')
+assert str_cmp('GoOd DaY', 'gOOd dAy')
+assert not str_cmp('how', 'who')
+
+print('all tests passed')
diff --git a/exercise_solutions/q2c_str_same_letters.py b/exercise_solutions/q2c_str_same_letters.py
new file mode 100755
index 0000000..cbca962
--- /dev/null
+++ b/exercise_solutions/q2c_str_same_letters.py
@@ -0,0 +1,12 @@
+#!/usr/bin/python3
+
+def str_anagram(s1, s2):
+ return sorted(s1.lower()) == sorted(s2.lower())
+
+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')
+
+print('all tests passed')
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
new file mode 100755
index 0000000..557cd47
--- /dev/null
+++ b/exercise_solutions/q3a_6by7.py
@@ -0,0 +1,24 @@
+#!/usr/bin/python3
+
+def six_by_seven(num):
+ if num % 42 == 0:
+ return 'Universe'
+ elif num % 7 == 0:
+ return 'Good'
+ elif num % 6 == 0:
+ return 'Food'
+ else:
+ return 'Oops'
+
+assert six_by_seven(66) == 'Food'
+assert six_by_seven(13) == 'Oops'
+assert six_by_seven(42) == 'Universe'
+assert six_by_seven(14) == 'Good'
+assert six_by_seven(84) == 'Universe'
+assert six_by_seven(235432) == 'Oops'
+
+print('all tests passed')
+
+## bonus
+#for num in range(1, 101):
+# print(num, six_by_seven(num))
diff --git a/exercise_solutions/q3b_dec_bin.py b/exercise_solutions/q3b_dec_bin.py
new file mode 100755
index 0000000..3349154
--- /dev/null
+++ b/exercise_solutions/q3b_dec_bin.py
@@ -0,0 +1,18 @@
+#!/usr/bin/python3
+
+for n in range(1, 1001):
+ dec_n = str(n)
+ bin_n = format(n, 'b')
+ if dec_n == dec_n[::-1] and bin_n == bin_n[::-1]:
+ print(dec_n, bin_n)
+
+ #oct_n = format(n, 'o')
+ #if dec_n == dec_n[::-1] and bin_n == bin_n[::-1] and oct_n == oct_n[::-1]:
+ # print('{0:d} {0:#b} {0:#o}'.format(n))
+
+ #oct_n = format(n, 'o')
+ #hex_n = format(n, 'x')
+ ##if all((dec_n == dec_n[::-1], bin_n == bin_n[::-1], oct_n == oct_n[::-1], hex_n == hex_n[::-1])):
+ #if dec_n == dec_n[::-1] and bin_n == bin_n[::-1] and \
+ # oct_n == oct_n[::-1] and hex_n == hex_n[::-1]:
+ # print('{0:d} {0:#b} {0:#o} {0:#x}'.format(n))
diff --git a/exercise_solutions/q4a_iter_product.py b/exercise_solutions/q4a_iter_product.py
new file mode 100755
index 0000000..84aa33c
--- /dev/null
+++ b/exercise_solutions/q4a_iter_product.py
@@ -0,0 +1,16 @@
+#!/usr/bin/python3
+
+def product(ip_iterable):
+ op = 1
+ for n in ip_iterable:
+ op *= n
+ return op
+
+assert product([1, 4, 21]) == 84
+assert product([-4, 2.3e12, 77.23, 982, 0b101]) == -3.48863356e+18
+assert product((-3, 11, 2)) == -66
+assert product({8, 300}) == 2400
+assert product([234, 121, 23, 945, 0]) == 0
+assert product(range(1, 6)) == 120
+
+print('all tests passed')
diff --git a/exercise_solutions/q4b_lowest_value.py b/exercise_solutions/q4b_lowest_value.py
new file mode 100755
index 0000000..7850568
--- /dev/null
+++ b/exercise_solutions/q4b_lowest_value.py
@@ -0,0 +1,16 @@
+#!/usr/bin/python3
+
+def nth_lowest(ip_iterable, n=1):
+ return sorted(set(ip_iterable))[n-1]
+
+nums = [42, 23421341, 234.2e3, 21, 232, 12312, -2343]
+assert nth_lowest(nums, 3) == 42
+assert nth_lowest(nums, 5) == 12312
+
+nums = [1, -2, 4, 2, 1, 3, 3, 5]
+assert nth_lowest(nums) == -2
+assert nth_lowest(nums, 4) == 3
+
+assert nth_lowest('unrecognizable', 3) == 'c'
+
+print('all tests passed')
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
new file mode 100755
index 0000000..cefbf05
--- /dev/null
+++ b/exercise_solutions/q5a_col_sum.py
@@ -0,0 +1,13 @@
+#!/usr/bin/python3
+
+with open('f1.txt', 'r', encoding='ascii') as f:
+ total = 0
+ for line in f:
+ 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/q5b_sum_ints.py b/exercise_solutions/q5b_sum_ints.py
new file mode 100755
index 0000000..9050aad
--- /dev/null
+++ b/exercise_solutions/q5b_sum_ints.py
@@ -0,0 +1,14 @@
+#!/usr/bin/python3
+
+import re
+
+with open('f2.txt', 'r', encoding='ascii') as f:
+ total = 0
+ for line in f:
+ total += sum(int(n) for n in re.findall(r'\d+', line))
+
+ assert total == 2298
+
+#assert sum(int(n) for n in re.findall(r'\d+', open('f2.txt', encoding='ascii').read())) == 2298
+
+print('test passed')
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/q6a_one_char_diff.py b/exercise_solutions/q6a_one_char_diff.py
new file mode 100755
index 0000000..9cb26a5
--- /dev/null
+++ b/exercise_solutions/q6a_one_char_diff.py
@@ -0,0 +1,31 @@
+#!/usr/bin/python3
+
+def is_one_char_diff(word1, word2):
+ if len(word1) != len(word2):
+ return False
+
+ word1, word2 = word1.lower(), word2.lower()
+ for i in range(len(word1)):
+ if word1[0:i] + word1[i+1:] == word2[0:i] + word2[i+1:]:
+ return True
+
+ return False
+
+assert is_one_char_diff('bar', 'car')
+assert is_one_char_diff('bar', 'Bat')
+assert is_one_char_diff('bar', 'bar')
+assert is_one_char_diff('bar', 'baZ')
+assert is_one_char_diff('A', 'b')
+
+assert not is_one_char_diff('a', '')
+assert not is_one_char_diff('bar', 'bark')
+assert not is_one_char_diff('bar', 'Art')
+assert not is_one_char_diff('bar', 'bot')
+assert not is_one_char_diff('ab', '')
+
+assert is_one_char_diff('Food', 'good')
+assert is_one_char_diff('food', 'fold')
+assert not is_one_char_diff('food', 'Foody')
+assert not is_one_char_diff('food', 'fled')
+
+print('all tests passed')
diff --git a/exercise_solutions/q6b_alpha_order.py b/exercise_solutions/q6b_alpha_order.py
new file mode 100755
index 0000000..5badf74
--- /dev/null
+++ b/exercise_solutions/q6b_alpha_order.py
@@ -0,0 +1,23 @@
+#!/usr/bin/python3
+
+def is_alpha_order(word):
+ word = word.lower()
+ return list(word) == sorted(word) or list(word) == sorted(word, reverse=True)
+
+assert is_alpha_order('bot')
+assert is_alpha_order('art')
+assert is_alpha_order('toe')
+assert is_alpha_order('AborT')
+
+assert not is_alpha_order('are')
+assert not is_alpha_order('boat')
+assert not is_alpha_order('Flee')
+
+# sentence
+def is_alpha_order_sentence(sentence):
+ return all(is_alpha_order(word) for word in sentence.split())
+
+assert is_alpha_order_sentence('Toe got bit')
+assert not is_alpha_order_sentence('All is well')
+
+print('all tests 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/q7_misc.py b/exercise_solutions/q7_misc.py
new file mode 100755
index 0000000..f59d22f
--- /dev/null
+++ b/exercise_solutions/q7_misc.py
@@ -0,0 +1,8 @@
+#!/usr/bin/python3
+
+import subprocess
+# provide path to your favorite media player and file to play
+subprocess.call(['vlc', r'/path/to/file'])
+
+import webbrowser
+webbrowser.open(r'https://github.com/learnbyexample/Python_Basics')
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")
+