17  Nurturing Session II

%load_problem sumifs
Problem: SUMIFS

Implement excel function SUMIFS as a function in python. SUMIFS(sum_list, criteria_list, condition). Here first argument is the list on which sum will be performed. Second argument is the list on which condition is checked, and third argument is condition as a string , as in excel.

possible values of condition are

 - <  less than
 - <= less than or equal to
 - >  greater than
 - >= greater than or equal to
 - <>  not equal to
 - empty means equal

Sample run is shown below. For simplicity assume that all the data consists of integers.

  >>> d = [1,2,3,4,5,4,4,5]
  >>> a = [10,20,30,40,50,40,40,50]
  >>> SUMIFS(d, a, "<40")
  6
  >>> SUMIFS(d, a, ">=40")
  22
  >>> SUMIFS(d, a, "40")
  12
  >>> SUMIFS(d, a, "<>40")
  16

You can verify your solution using:

%verify_problem sumifs

# your code here

# approach 1

def check_cond(n, cond):
    if cond.startswith("<"): # "<40"
        return n < int(cond.replace("<",""))
    elif cond.startswith("<="): # "<=40"
        return n <= int(cond.replace("<=",""))
    # complete this....
    
def check_cond_find_indices(condlist, cond):
    return [i for i, item in enumerate(condlist) if check_cond(item, cond)]
        
def sumifs(sumlist, condlist, cond):
    indices = check_cond_find_indices(condlist, cond)
    return sum_based_on_indices(indices , sumlist)



# approach 2
def sumifs((sumlist, condlist, cond):
    s = 0 
    for x, c in zip(sumlist, condlist):
        if condition_check(c, cond):
            s += x
    return s
d = [1,2,3,4,5,4,4,5]
a = [10,20,30,40,50,40,40,50]

consider the case “<40” indices which are less than 40 in a are 0, 1, 2 d[0] + d[1] + d[2] 1 + 2 + 3 6

for x, y in zip(a, d):
    print(x, y)
10 1
20 2
30 3
40 4
50 5
40 4
40 4
50 5
%load_problem paste-cmd
Problem: Paste

Write a program paste.py that takes two files as command-line arguments and contacenates the corresponding lines in those two files with a tab character and prints it.

For example, of the first file files/a.txt has the following contents:

A
B
C
D

and the second file files/b.txt has the following:

1
2
3
4

The output should be:

$ python paste.py files/a.txt files/b.txt
A       1
B       2
C       3
D       4

Note that the first line is "A\t1".

For simplicity, assume that both the files have exactly same number of lines.

Hint:

You can use the strip method on a string to remove the new line character.

>>> "a\n".strip("\n")
"a"

You can verify your solution using:

%verify_problem paste-cmd

%%file paste.py
# your code here
import sys

def paste(filepath1, filepath2):
    with open(filepath1) as f1:
        with open(filepath2) as f2:
            for line1, line2 in zip(f1, f2):
                print(f"{line1}\t{line2}") # Handle new lines in line1 and line2

if __name__ == "__main__":
    file1, file2 = sys.argv[1], sys.argv[2]
    paste(file1, file2)
Writing paste.py
with open("poem.txt") as f:
    for line in f:
        print(line) # line contains extra new line character... 
                     #you will have to remove it with strip
The Zen of Python, by Tim Peters



Beautiful is better than ugly.

Explicit is better than implicit.

Simple is better than complex.

Complex is better than complicated.

Flat is better than nested.

Sparse is better than dense.

Readability counts.

Special cases aren't special enough to break the rules.

Although practicality beats purity.

Errors should never pass silently.

Unless explicitly silenced.

In the face of ambiguity, refuse the temptation to guess.

There should be one-- and preferably only one --obvious way to do it.

Although that way may not be obvious at first unless you're Dutch.

Now is better than never.

Although never is often better than *right* now.

If the implementation is hard to explain, it's a bad idea.

If the implementation is easy to explain, it may be a good idea.

Namespaces are one honking great idea -- let's do more of those!
%load_problem group
Problem: Group

Write a function group that take a list of values and splits into smaller lists of given size.

>>> group([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

>>> group([1, 2, 3, 4, 5, 6, 7, 8, 9], 4)
[[1, 2, 3, 4], [5, 6, 7, 8], [9]]

You can verify your solution using:

%verify_problem group

# your code here


hint : make use of slicing

d
[1, 2, 3, 4, 5, 4, 4, 5]
d[1:4]
[2, 3, 4]
start = 0
size = 3
d = list(range(9))
numberofgrps = 3
groups  = []
for i in range(numberofgrps):
    start = start + i*size
    print(d[start:start+size])
[0, 1, 2]
[3, 4, 5]
[]
%load_problem anagrams
Problem: Anagrams

Write a functions anagrams to find anagrams in a given list of words.

Two words are called anagrams if one word can be formed by rearranging letters of another. For example eat, ate and tea are anagrams.

>>> anagrams(['eat', 'ate', 'done', 'tea', 'soup', 'node'])
[['eat', 'ate', 'tea'], ['done', 'node'], ['soup']]

You can verify your solution using:

%verify_problem anagrams

# your code here

def signature(word):
    return "".join(sorted(word))

def anagrams(words):
    ana = {}
    for word in words:
        sig = signature(word) 
        if sig in ana:
            ana[sig].append(word) # can you make use setdefault 
        else:
            ana[sig] = [word]
    return [v for v in ana.values()]
sorted("ate")
['a', 'e', 't']
sorted("eat")
['a', 'e', 't']
signature("eat")
'aet'
signature("ate")
'aet'
anagrams(['eat', 'ate', 'done', 'tea', 'soup', 'node'])
[['eat', 'ate', 'tea'], ['done', 'node'], ['soup']]
%load_problem reverse-digits
Problem: Reverse Digits

Write a function reverse_digits which will reverse digits of multi digit number and return new formed integer.

>>> reverse_digits(1234)
4321

You can verify your solution using:

%verify_problem reverse-digits

# your code here


%load_problem split
Problem: Split a File

Write a program split.py that splits a large file into multiple smaller files. The program should take a filename and the number of lines as arguments and write multiple small files each containing the specified number of lines (The last one may have smaller number of lines).

Suppose you have a file 100.txt in the current directory. (you can copy it from files/100.txt).

$ cp files/100.txt 100.txt

When you run split.py with that, it should split that into multiple small files.

$ python split.py 100.txt 30
writing 100-part1.txt
writing 100-part2.txt
writing 100-part3.txt
writing 100-part4.txt

You can verify your solution using:

%verify_problem split

%%file split.py
# your code here


def makefilename(file, count):
    base = "".join(file.split(".")[:-1])
    return f"base-part{count}.txt"

def writefile(data, originalfilename, count):
    filename = makefilename(originalfilename, count)
    with open(filename, "w") as f:
        for line in data:
            f.write(line)


def split(filename, size):
    count = 1
    with open(filename) as f:
        buffer = []
        for line in f:
            buffer.append(line)
            if len(buffer) == size:
                writefile(buffer, filename, count)
                buffer = []
                count += 1
               

if __name__ == "__main__":
    filename, size = sys.argv[1], int(sys.argv[2])
    split(filename, size)

Writing split.py
%load_problem user-class
Problem: User Class

Create a python class User to represent user on a website which has username and password. The class should have two fields name and password.

The class should have following methods

  1. set_password - This will allow user to reset new password
  2. check_password - This checks correctness of given password with user's password.
>>> user = User("hans_solo", "test123")
>>> user.check_password("Ty^&gsHJk")
False
>>> user.check_password("test123")
True
>>> user.set_passowrd("pass123")
>>> user.check_password("hello123")
False
>>> user.check_password("pass123")
True

You can verify your solution using:

%verify_problem user-class

# your code here


class Pair:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def set_x(self, x):
        self.x = x

    def get_x(self):
        return self.x

    def check_x(self, xvalue):
        return self.x == xvalue
%load_problem tail
Problem: command tail

Write a python script tail.py which mimics unix command tail. The command will show last few lines of a file. It takes filename as argument and optional argument -n to specify how many lines to display. By default it displays last 5 lines of the file.

$ python tail.py --help
usage: tail.py [-h] [-n LINES] filename

positional arguments:
  filename              path to the file

options:
  -h, --help            show this help message and exit
  -n LINES, --lines LINES
                        Number of lines to display

$ python tail.py files/ten.txt
6
7
8
9
10

$ python tail.py -n 3 files/ten.txt
8
9
10

$ python tail.py zen.txt
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

You can verify your solution using:

%verify_problem tail

%%file tail.py
# your code here
import collections
d = collections.deque(maxlen=5)
d.append(1)
d.append(2)
d
deque([1, 2], maxlen=5)
d.append(3)
d.append(4)
d.append(5)
d
deque([1, 2, 3, 4, 5], maxlen=5)
d.append(10)
d
deque([2, 3, 4, 5, 10], maxlen=5)
%%file tail.py
import typer
from typing_extensions import Annotated

def tail(filename: str, lines:Annotated[int, typer.Option("--lines", "-n")]= 5):
    print(filename , lines)


if __name__ == "__main__":
    typer.run(tail)
Writing tail.py
!python tail.py --help
                                                                                

 Usage: tail.py [OPTIONS] FILENAME                                              

                                                                                

╭─ Arguments ──────────────────────────────────────────────────────────────────╮

│ *    filename      TEXT  [required]                                          │

╰──────────────────────────────────────────────────────────────────────────────╯

╭─ Options ────────────────────────────────────────────────────────────────────╮

│ --lines  -n      INTEGER  [default: 5]                                       │

│ --help                    Show this message and exit.                        │

╰──────────────────────────────────────────────────────────────────────────────╯


!python tail.py poem.txt -n 5
poem.txt 5