<jupyter_start><jupyter_text>Mini Project: Sorting and Evaluating Math Expressions  Week 3 **Q1.** *Mergesort:* Modify your `mergesort(array)` function that you did in your cohort session to take one additional argument called `byfunc`, i.e. `mergesort(array, byfunc)`. If the caller does not specify the value of `byfunc`, its default value is `None`. When this argument is `None`, the function `mergesort` behaves similar to your cohort session by sorting the array according to its values. However, when the value of this argument is not `None` but rather some other function, your `mergesort` function should sort the array according to the value returned by this function. For example, instead of sorting an array of integers, we want to sort an array of tupple.```pythonarray = [(1, 2), (3, 2), (2, -1), (4, 7), (-1, -2)]```We can define a function say `select()` as follows:```pythondef select(item):    return item[0]```You can then should be able to call your `mergesort()` function in the following:```pythonmergesort(array, select)```which will sort the list of tuples according to the value of its *first* element (recall `item[0]` in `select()`). This means that if you want to sort based on the *second* element of the tuple, you can redefine select as:```pythondef select(item):    return item[1]```You can also apply this to a list of objects, say `User` class objects.```pythonarray = [, , , ..., ]```You can define the following `select()` function to sort according to its `username` attribute.```pythondef select(item):    return item.username```You can then call the `mergesort()` function as follows:```pythonmergesort(array, select)```Python allows you to write [lambda functions](https://realpython.com/python-lambda/) to replace your `select()` function definition. You can simply call merge sort with the following without defining `select()`.```pythonmergesort(array, lambda item: item.username)```<jupyter_code>def mergesort(array, byfunc=None):
  pass
array = [(1, 2), (3, 2), (2, -1), (4, 7), (-1, -2)]
mergesort(array, lambda item: item[0])
assert array == [(-1, -2), (1, 2), (2, -1), (3, 2), (4, 7)]
mergesort(array, lambda item: item[1])
assert array == [(-1, -2), (2, -1), (1, 2), (3, 2), (4, 7)]<jupyter_output><empty_output><jupyter_text>**Q2.** Create a class called `EvaluateExpression` to evaluate mathematical expressions for Integers. The class has the following property:- `expression`: which is a property with a get and set method. The set method of this property should check if the string contains any invalid characters. If there is any invalid character, it should set the internal property `expr` to an empty String. Otherwise, it should set the string as it is. Valid characters are: `0123456789+-*/()` and an empty space.- `expr`: which is a property that stores only valid expression. It is used internally to store the expression.During object instantiation, a string can be passed on to `__init__()`.- `__init__(expr)`: where expr is the mathematical expression to initialize the property `expr`. If nothing is provided it should initialize to an empty String. If the string contains other characters besides those in the valid characters list above, the property `expr` should be initialized to an empty string.<jupyter_code>class EvaluateExpression:
  valid_char = '0123456789+-*/() '

  def __init__(self, string=""):
    self.string=string
    
    pass

  @property
  def expression(self):
    return self.string
    pass

  @expression.setter
  def expression(self, new_expr):
    for each in new_expr:
      if each not in EvaluateExpression.valid_char:
        self.string=""
        print("mod:",new_expr,self.string)
        return self.string
      else:
        print(new_expr)
        self.string=new_expr
        pass
    pass

  
def containsAny(str, set):
  for each in str:
    if each not in set:
      str=""
      return str
    else:
      print(each)
      pass
      

set = '0123456789+-*/() '
str="3 * 4"


containsAny(str,set)



expr1 = EvaluateExpression()
assert expr1.expression == ""
expr2 = EvaluateExpression("1 + 2")
assert expr2.expression == "1 + 2"
expr2.expression = "3 * 4"
assert expr2.expression == "3 * 4"
expr2.expression = "3 & 4"
assert expr2.expression == ""<jupyter_output>3 * 4
3 * 4
3 * 4
3 * 4
3 * 4
3 & 4
3 & 4
mod: 3 & 4<jupyter_text>**Q3.** The class `EvaluateExpression` also has the following method:- `insert_space()`: which is used to insert one empty space before an operator and another empty space after the operator in the `expression` property. The function should return a new String. Note that this means that if there are two operators side by side, there will be two empty space between them.<jupyter_code># class EvaluateExpression:
#   # copy the other definitions
#   # from the previous parts

#   def insert_space(self):
    
#     pass
class EvaluateExpression:
  valid_char = '0123456789+-*/() '

  def __init__(self, string=""):
    self.string=string
    
    pass

  @property
  def expression(self):
    return self.string
    pass

  @expression.setter
  def expression(self, new_expr):
    for each in new_expr:
      if each not in EvaluateExpression.valid_char:
        self.string=""
        print("mod:",new_expr,self.string)
        return self.string
      else:
        print(new_expr)
        self.string=new_expr
        return self.string
        pass
    pass

  operator_char="+-*/()"

  def insert_space(self):
    
    self.string= self.string.replace(" ", "")
    print(self.string)

    stringlist=list(self.string)
    new_stringlist=stringlist.copy()


    operator_char="+-*/()"
    internal_counter=0

    for i in range(1,len(stringlist)):
  
      if stringlist[i-1] in operator_char and  stringlist[i] in operator_char:
        print("TGT",i)
        new_stringlist.insert(i+internal_counter," ") #idx is always moving!!!!!
        internal_counter+=1
    
    # 

      print(stringlist[i],i)
      i=i+1

    print("new_stringlist",new_stringlist)
    self.string="".join(new_stringlist)
    print("".join(new_stringlist))

    new_string="".join(new_stringlist)
    # self.string = " " + " ".join(self.string for self.string in self.string)+" "
    
    valid_alpha = '0123456789+-*/()'

    new_string2=""
    for each in new_string:
      if each in valid_alpha: 
        new_string2=new_string2 + " "+each
      elif each == " ":
        new_string2=new_string2 + " "

    print(new_string2)
    new_string2=new_string2+" "
    self.string= new_string2
    print("incall",self.string)
    return self.string
    pass
# operator_char="+-*/()"
# string=" ( ( 1 + 2 ) * 3 / ( 4 - 5 ) ) "
# #      " (  ( 1 + 2 )  * 3 /  ( 4 - 5 )  ) "
# counter=0
# repeat=0
# new_string=""
# length=len(string)-1
# print("length",length)
# for each in string:
#   print("each [",each,"]")
#   if counter==1 and each != " ":
#     repeat+=1
#     if repeat==3:
#         counter=0
#         repeat=0
#     print("repeat",repeat)
#   if each in operator_char:
#     counter+=1
#     if counter==2:
#       print("changed")
#       each=" "+each
#       counter=0
  

#   new_string=new_string+each




#       # print(each)
#   print(new_string, counter)

### THIS IS GOOD PIECE
operator_char="+-*/()"

string="((1+2)*3/(4-5))"
# string=" ( 1 + 2 ) "
# 
string= string.replace(" ", "")
print(string)
new_string=""

print(string)
print(len(string)-1)
stringlist=list(string)
print(stringlist)
print("".join(stringlist))


print(string)
new_stringlist=stringlist.copy()
print(len(stringlist))

internal_counter=0

for i in range(1,len(stringlist)):
  
  if stringlist[i-1] in operator_char and  stringlist[i] in operator_char:
    print("TGT",i)
    new_stringlist.insert(i+internal_counter," ") #idx is always moving!!!!!
    internal_counter+=1
    
# 

  # print(stringlist[i],i)
  i=i+1

# print("stringlist",stringlist)
# # print("".join(stringlist))

print("new_stringlist",new_stringlist)
print("".join(new_stringlist))



print("new_string",new_string)


new_string="".join(new_stringlist)
## NOW THIS PROBLEM@@@

print(new_string)
# new_string == " (  ( 1 + 2 )  * 3 /  ( 4 - 5 )  ) "

valid_alpha = '0123456789+-*/()'

new_string2=""
for each in new_string:
  if each in valid_alpha: 
    new_string2=new_string2 + " "+each
  elif each == " ":
    new_string2=new_string2 + " "

print("new_string2",new_string2)
new_string2=new_string2+" "



new_string2== " (  ( 1 + 2 )  * 3 /  ( 4 - 5 )  ) "
expr1 = EvaluateExpression("(1+2)")
expr1.insert_space() == " ( 1 + 2 ) "
expr1 = EvaluateExpression("(1+2)")
assert expr1.insert_space() == " ( 1 + 2 ) "
expr1.expression = "((1+2)*3/(4-5))"
assert expr1.insert_space() == " (  ( 1 + 2 )  * 3 /  ( 4 - 5 )  ) "<jupyter_output>(1+2)
1 1
+ 2
2 3
) 4
new_stringlist ['(', '1', '+', '2', ')']
(1+2)
 ( 1 + 2 )
incall  ( 1 + 2 ) 
((1+2)*3/(4-5))
((1+2)*3/(4-5))
TGT 1
( 1
1 2
+ 3
2 4
) 5
TGT 6
* 6
3 7
/ 8
TGT 9
( 9
4 10
- 11
5 12
) 13
TGT 14
) 14
new_stringlist ['(', ' ', '(', '1', '+', '2', ')', ' ', '*', '3', '/', ' ', '(', '4', '-', '5', ')', ' ', ')']
( (1+2) *3/ (4-5) )
 (  ( 1 + 2 )  * 3 /  ( 4 - 5 )  )
incall  (  ( 1 + 2 )  * 3 /  ( 4 - 5 )  )<jupyter_text>Week 4 **Q4.** The class `EvaluateExpression` also has the following methods:- `process_operator(operand_stack, operator_stack)`: which process one operator. This method should modify the Stacks provided in the arguments. Note that the division operator `/` should be considered as an integer division for this exercise. This means that you need to use `//` in Python.<jupyter_code>class Stack:
  pass

class EvaluateExpression:
  # copy the other definitions
  # from the previous parts

  def process_operator(self, operand_stack, operator_stack):
    pass

    
  
expr1 = EvaluateExpression()
operand_stack = Stack()
operator_stack = Stack()
operand_stack.push(3)
operand_stack.push(4)
operator_stack.push("+")
expr1.process_operator(operand_stack, operator_stack)
assert operand_stack.peek() == 7
operand_stack.push(5)
operator_stack.push("*")
expr1.process_operator(operand_stack, operator_stack)
assert operand_stack.peek() == 35
operand_stack.push(30)
operator_stack.push("-")
expr1.process_operator(operand_stack, operator_stack)
assert operand_stack.peek() == 5
operand_stack.push(2)
operator_stack.push("/")
expr1.process_operator(operand_stack, operator_stack)
assert operand_stack.peek() == 2<jupyter_output><empty_output><jupyter_text>**Q5.** The class `EvaluateExpression` also has the following methods:- `evaluate()`: which evaluate the mathematical expression contained in the property `expression`. The method should return an Integer. This method contains two processes:    - Phase 1: In this phase, the code scans the expression from left to right to extract operands, operators, and the parentheses.        1. If the extracted character is an operand, push it to `operand_stack`.        1. If the extracted character is + or - operator, process  all the operators at the top of the `operator_stack` and push the extracted operator to `operator_stack`. You should process all the operators as long as the `operator_stack` is not empty and the top of the `operator_stack` is not `(` or `)` symbols.        1. If the extracted character is a `*` or `/` operator, process all the `*` or `/` operators at the top of the `operator_stack` and push the extracted operator to `operator_stack`.         1. If the extracted character is a `(` symbol, push it to `operator_stack`.        1. If the extracted character is a `)` symbol, repeatedly process the operators from the top of `operator_stack` until seeing the `(` symbol on the stack.     - Phase 2: Repeatedly process the operators from the top of `operator_stack` until `operator_stack` is empty.<jupyter_code>class EvaluateExpression:
  # copy the other definitions
  # from the previous parts

  def evaluate(self):
    operand_stack = Stack()
    operator_stack = Stack()
    expression = self.insert_space()
    tokens = expression.split()
    pass
expr1 = EvaluateExpression("(1+2)*3")
assert expr1.evaluate() == 9
expr1.expression = "(1 + 2) * 4 - 3"
assert expr1.evaluate() == 9
expr2 = EvaluateExpression("(1+2 *4-  3)* (7/5 * 6)")
assert expr2.evaluate() == 36<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13519260652653964, 'fraction_numerical': 0.029606608325836264, 'mean_word_length': 3.7765625, 'pattern_counts': {'":': 3, '<': 19, '<?xml version=': 0, '>': 19, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '579481', 'license': 'MIT', 'n_tokens_mistral': 4131, 'n_tokens_neox': 3874, 'n_words': 1283, 'path': 'mp_calc/mp2_exercises_JA.ipynb', 'repo_name': 'ang-jason/fip_powerx_mini_projects-foxtrot', 'size': '12227'}