Why does replace not work for this substring? - python

I'm completely new to Python so I apologize in advance for the bad code. I am trying to replace the sub-string of strings in a list, but the list returned is the same. In the code, there is a list of accounts and another list of transactions given by the user, I am supposed to check whether the command is add or sub to either add to balance or subtract from it. Here is the format for both lists:
F1:
ACCOUNT NUMBER | PIN CODE | BALANCE
F2:
COMMAND | AMOUNT | ACCOUNT NUMBER | PIN CODE
And here is the code I'm working with:
import sys
F1 = sys.argv[1]
F2 = sys.argv[2]
def file_reader(filePath):
file = open(filePath, 'r')
data = file.read()
file.close()
return data
def splitter(str):
return str.split()
def joiner(list):
return '\n'.join(list)
def file_writer(filePath, str):
file = open(filePath, 'w')
file.write(str)
def make_transaction(accounts, transactions):
for i in range(len(transactions)):
# Check account number & pin
if accounts[i][0:4] == transactions[i][9:13]:
if accounts[i][5:9] == transactions[i][-4:]:
# Check whether the user wants to add or subtract from balance
if 'add' in transactions[i]:
balance = int(accounts[i][10:])
transaction = int(transactions[i][4:8])
balance += transaction
balanceStr = str(balance)
accounts[i].replace(accounts[i][10:], balanceStr)
elif 'sub' in transactions[i]:
balance = int(accounts[i][10:])
transaction = int(transactions[i][4:8])
if balance > transaction:
balance -= transaction
balanceStr = str(balance)
accounts[i].replace(accounts[i][10:], balanceStr)
return accounts
file1 = fileReader(F1)
file1Data = splitter(file1)
file2 = fileReader(F2)
file2Data = splitter(file2)
print(file1Data)
print(file2Data)
print(makeTransaction(file1Data, file2Data))
This is the output:
Program Failed for Input: /tmp/a1 /tmp/tx
Expected Output:
Your Program Output: ['1000|1234|10000', '1020|2222|0', '3000|3344|1000', '2020|1234|90000']
['add|1000|1000|1234', 'sub|1000|1020|2222', 'sub|1000|3000|3344']
['1000|1234|10000', '1020|2222|0', '3000|3344|1000', '2020|1234|90000']
Any help would be appreciated. Thank you.

.replace is risky use case for what you are trying to solve. It is using pattern matching to do the change and in case your substring, i.e. balance[10:] would accidentally match with balance[0:5] you would silently get very unpredictable results.
Instead, I would suggest you replace
accounts[i].replace(accounts[i][10:], balanceStr)
with
accounts[i] = accounts[i][0:9] + balanceStr
On an extra note, assuming that your input data is split by |, you should create data holders for your input data instead of working with the strings as the strings are volatile and your code could start acting unpredictably if your customer performed a very large transaction.
I would instead do something along the lines of account_number, pin_code, balance = '1000|1234|10000'.split('|') and load the data into a list where each row would represent one account or one transaction.

This should be completely rewritten:
def make_transaction(accounts, transactions):
for i in range(len(transactions)):
# Check account number & pin
if accounts[i][0:4] == transactions[i][9:13]:
if accounts[i][5:9] == transactions[i][-4:]:
# Check whether the user wants to add or subtract from balance
if 'add' in transactions[i]:
balance = int(accounts[i][10:])
transaction = int(transactions[i][4:8])
balance += transaction
balanceStr = str(balance)
accounts[i].replace(accounts[i][10:], balanceStr)
elif 'sub' in transactions[i]:
balance = int(accounts[i][10:])
transaction = int(transactions[i][4:8])
if balance > transaction:
balance -= transaction
balanceStr = str(balance)
accounts[i].replace(accounts[i][10:], balanceStr)
return accounts
for example, I'd rather do this:
def make_transaction(accounts, transactions) :
for a,t in zip(accounts, transactions) :
a_num, a_pin, a_balance = a.split('|')
t_command, t_amount, t_num, t_pin = t.split('|')
if a_num == t_num and a_pin == t_pin :
if t_command == 'add' :
# do your stuff
elif t_command == 'sub' :
# do something else
and so on... working on string with indices is very very wrong.

Related

Add \n to the last element of a list, in a list of lists

So I have an external file where each line has a task formatted like this:
User, Title of task, Description of task, Date assigned, Due date, Completed (Yes/No)
I have created a list of lists where within the main list are lists of the line above essentially where each element is separated from the ", ".
So it looks like this:
[['User', 'Title of task', 'Description of task', 'Date assigned', 'Due Date', 'Completed (Yes/No)']]
I am trying to change the last element of the last list to include "\n" at the end.
Here is the code I implemented:
with open('tasks.txt', 'w') as f2:
count = 0
for i in list_of_tasks:
count += 1
if count == len(list_of_tasks):
list_of_tasks[i][-1] = str(f"{list_of_tasks[i][-1]}\n")
f2.write(", ".join(i))
else:
f2.write(", ".join(i))
This is the error I get:
list_of_tasks[i][-1] = str(f"{list_of_tasks[i][-1]}\n")
~~~~~~~~~~~~~^^^
TypeError: list indices must be integers or slices, not list
Ultimately what I'm trying to achieve is to edit parts of each line in this external file. The initial problem I have is the spacing after writing to the file gets messed up hence I'm trying to figure out how to add \n to the last element in the final list in this list of lists.
if it helps here's the full function:
def view_mine(username):
# opens tasks.txt as read only and assigns it as a variable named f1
with open('tasks.txt', 'r') as f1:
x = 1
list_of_tasks= []
other_tasks = []
for line in f1:
line3_split = line.split(', ')
if line3_split[0] == username:
user_format = f"""
Task {x}: {line3_split[1]}
Assigned to: {line3_split[0]}
Date assigned: {line3_split[3]}
Due date: {line3_split[4]}
Task complete? {line3_split[5]}
Task description:
{line3_split[2]}
"""
print(user_format)
x += 1
list_of_tasks.append(line3_split)
else:
other_tasks.append(line3_split)
selection = int(input(f"Which task do you want to select (1-{x-1}) or enter '-1' to return to main menu? ")) -1
if selection == -2:
return
else:
mark_or_edit = int(input(f"To mark as complete enter '1'. To edit the task enter '2'."))
if mark_or_edit == 1:
if list_of_tasks[selection][-1] == "No":
list_of_tasks[selection][-1] = "Yes\n"
elif list_of_tasks[selection][-1] == "No\n":
list_of_tasks[selection][-1] = "Yes\n"
elif mark_or_edit == 2:
user_or_date = int(input("To edit user assigned enter '1'. To edit due date enter '2'. "))
if user_or_date == 1:
user_check = input("Which user do you want to assign this task to? ")
existing_names_list = []
with open('user.txt', 'r') as f:
for line in f:
existing_names = line.split(', ')
existing_names_list.append(existing_names[0])
if user_check in existing_names_list:
list_of_tasks[selection][0] = user_check
else:
print("User does not exist.")
elif user_or_date == 2:
new_date = input("Enter the new due date (XX XXX XXXX): ")
list_of_tasks[selection][4] = new_date
with open('tasks.txt', 'w') as f2:
count = 0
for I in list_of_tasks:
count += 1
if count == len(list_of_tasks):
list_of_tasks[i][-1] = str(f"{list_of_tasks[i][-1]}\n")
f2.write(", ".join(i))
else:
f2.write(", ".join(i))
for i in other_tasks:
f2.write(", ".join(i))
return
The problem is in:
for i in list_of_tasks
In this situation i is a string and you are trying to use it as index in your list.
I am not sure what you are trying to accomplish exactly, but I think your code has some logical inaccuracies. I hope the following code gets the job done:
with open('tasks.txt', 'w') as f2:
count = 0
for sub_list in list_of_tasks:
for i in range(len(sub_list)):
if i == len(sub_list) - 1:
temp = sub_list[i] + '\n'
f2.write(temp)
else:
f2.write(sub_list[i] + ', ')
The output of the above code for the list you provided is:
User, Title of task, Description of task, Date assigned, Due Date, Completed (Yes/No)
with '\n' included in the end, even if it is not obvious.
It seems that you have a list of lists, so a 2D array, in which every element is a string. So you have to loop once for the first dimension (so for each list) and then for the second dimension(so for each string in list). Having every string each time, you are able to create the sentence you are trying to.

How do I loop an if statement until needed and then stop

I want to loop my driver code and, once done, ask the user if they want to convert another currency into MYR. Everytime through the loop, save the returnedamount to an array and, once the user has no more currencies, they need to convert. The program will add the varibles in the array together to produce the sum and print that. If required, turn that sum into another currency.
class Currency_convertor:
rates = {}
def __init__(self, url):
data = requests.get(url).json()
self.rates = data["rates"]
def convert(self, from_currency, to_currency, amount):
initial_amount = amount
if from_currency != 'EUR' :
amount = amount / self.rates[from_currency]
amount = round(amount * self.rates[to_currency], 2)
print('{} {} = {} {}'.format(initial_amount, from_currency, amount, to_currency))
return amount
Driver code
if __name__ == "__main__":
YOUR_ACCESS_KEY = ''
url = 'https://api.exchangerate-api.com/v4/latest/USD'
c = Currency_convertor(url)
from_country = input("From Country: ")
to_country = input("TO Country: ")
amount = int(input("Amount: "))
returnedamount=c.convert(from_country, to_country, amount)
Use a while statement.
Example:
x = 0
while x < 10:
# This will keep running until the condition is no longer True
print(x)
x += 1
# Then run any final stuff outside of the loop
print('Finished looping')
Use "While" Instead of "if"

Python 3 Unit tests with user input

I'm absolutely brand new to Python unit test. I need to use it for a project I have to submit. I sort of have an idea of where to begin, it looks like we basically put in test parameters to functions we have defined in our program and we enter the expected result. If the expected result is output, we get OK, otherwise we will get Failure, or an error.
So my problem is that I have multiple user inputs stored into variables that are within for loops or while loops. I don't know where to even begin with this to set test values in for them.
Here is all of my code:
studentTripExpenses = {}
def dictCreate(studentAmount):
for i in range(0, studentAmount):
studentName = input("What is the name of the student? ")
expenseList = []
print("Enter 'done' to move to the next student.")
while True:
expense = input("What is the cost of this expense? ")
if expense.lower() == 'done':
break
elif (float(expense) >= 0) or (float(expense) < 0):
expenseList.append(float(expense))
elif not expense.isdigit():
print("Please enter a number or enter 'done' to move on.")
studentTripExpenses[studentName] = expenseList
return studentTripExpenses
def studentCost(dct):
for i in dct:
#Variable for individual costs of student
personalCost = 0
#Determines the total cost for each student
for x in dct[i]:
personalCost = personalCost + x
#Sets each students value to their total cost to two decimal places
dct[i] = float("%.2f" % personalCost)
return dct
def amountsDue(expenseLst, studentAvgPrice):
#Runs through the dictionary of students and individual total trip costs
for key in expenseLst:
maxPerson = max(expenseLst, key=expenseLst.get)
costDifference = 0
#Determines who owes who how much money
if max(expenseLst.values()) > expenseLst[key]:
costDifference = studentAvgPrice-expenseLst[key]
if (costDifference < 0):
costDifference = costDifference * -1
print("%s owes %s $%.2f" % (key, maxPerson, costDifference))
def main():
numOfStudents = int(input("How many students are going on the trip? "))
studentCostDict = dictCreate(numOfStudents)
studentTripExpenses = studentCost(studentCostDict)
totalCost = 0
#Gets the total cost for all students
for key in (studentTripExpenses):
totalCost = totalCost + studentTripExpenses[key]
#Changes the total cost to 2 decimal places
totalCost = float("%.2f" % totalCost)
#Determines the average amount spent per student
avgCost = float("%.2f" % (totalCost/len(studentTripExpenses)))
amountsDue(studentTripExpenses, avgCost)
main()
You can use mocking, where you replace a function or class with a test-supplied version. You can do this with the unittest.mock() module.
In this case, you can patch the input() name in your module; instead of the built-in function, the mock object will be called:
from unittest import mock
from unittest import TestCase
import module_under_test
class DictCreateTests(TestCase):
#mock.patch('module_under_test.input', create=True)
def testdictCreateSimple(self, mocked_input):
mocked_input.side_effect = ['Albert Einstein', '42.81', 'done']
result = dictCreate(1)
self.assertEqual(result, {'Albert Einstein': [42.81]})
Because input doesn't exist in your module (it is a built-in function), I told the mock.patch() decorator to create the name; now this input will be used instead of the built-in function.
The side_effect attribute lets you state multiple results; each time the mock is called, it'll return the next value in that list. So the first time 'Albert Einstein' is returned, the next time '42.81', etc.
Together, this lets you simulate actual user inputs.
If you do your test right, you'll notice that there is a bug in your function; the float() call will throw a ValueError exception when anything other than done or a valid numeric value is entered. You need to rework your code to account for that. Try with mocked_input.side_effect = ['Albert Einstein', 'Not an expense', '42.81', 'done'] to trigger the bug.
In case we do not have classes.
In the names.py file, we have the get_names function.
def get_names() -> list:
names = [str(input("Enter name: "))]
while str(input("Do you want to add another name")) == "Y":
names.append(str(input("Enter name: ")))
return categories
In the test_names.py file, we can write test like the following
import numpy as np
from unittest import mock
from src.main.names import get_names
#mock.patch('src.main.names.input', create=True)
def test_should_get_names_from_users(mocked_input):
mocked_input.side_effect = ["John", "Y", "Robert", "N"]
actual_names = get_names()
expected_names = ['John', "Robert"]
assert actual_names == expected_names

File Writing Python, How to print variable to txt file

I am trying to print a variable of a score and name to a .txt file using python.
import random
import csv
import operator
import datetime
now = datetime.datetime.now() ## gets the exact time of when the user begins the test.
def main():
global myRecord
myRecord = []
name = getNames()
myRecord.append(name)
record = quiz()
def getNames(): ## this function grabs first and lastname of the user
firstName = input ("Please enter your first name") ## asks for users name
surName = input("Please enter your surname") ## asks for users suername
space = " "
fullName = firstName + space +surName ## puts data of name together to make full name
print("Hello")
print (fullName)
myRecord.append(fullName)
return fullName ## this is a variable returned to main
def quiz():
print('Welcome. This is a 10 question math quiz\n')
score = 0 ## sets score to 0.
for i in range(10): ## repeats question 10 times
correct = askQuestion()## if the statement above if correct the program asks a question.
if correct:
score += 1## adds one to the score
print('Correct!\n')## prints correct if the user gets a question correct.
else:
print('Incorrect!\n') ## prints incorrect if the user gets a question wrong.
return 'Your score was {}/10'.format(score)
def randomCalc():
ops = {'+':operator.add, ## selects one of the three operators
'-':operator.sub, ## selects one of the three operators
'*':operator.mul,} ## selects one of the three operators
num1 = random.randint(0,12) ## samples a number between 0 and 12
num2 = random.randint(1,10) ## zero are not used to stop diving by zero
op = random.choice(list(ops.keys()))
answer = ops.get(op)(num1,num2)
print('What is {} {} {}?\n'.format(num1, op, num2)) ## puts together the num1, the operator and num2 to form question
return answer
def askQuestion():
answer = randomCalc()
guess = float(input())
return guess == answer
def myfileWrite (myrecord):
with open('Namescore.txt', 'w') as score:
score.write(fullName + '\n')
main()
here is the full code it should ask the users name, print 10 maths questions and then save the time name and score to a txt file
if you can help please do
many thanks
Your indentation is incorrect and you never actually call the function:
with open('Namescore.txt', 'w') as score:
score.write(fullName + '\n')
the code you wrote would IIRC re-make the file each time you ran the code. I believe this is the correct way to do it:
with open("Namescore.txt", "a") as file:
file.write(name, score, "\n") # I don't know what your vars are called
This will append to the file rather than rewrite :)
If you want to do it your way, the correct way would be:
def writeScore(name, score):
file = open("Namescore.txt", "a")
file.write(name, score, "\n")
file.close()
writeScore("Example Examlpus", 201)

How can we modify data that is in a shelve?

I have opened a shelve using the following code:
#!/usr/bin/python
import shelve #Module:Shelve is imported to achieve persistence
Accounts = 0
Victor = {'Name':'Victor Hughes','Email':'victor#yahoo.com','Deposit':65000,'Accno':'SA456178','Acctype':'Savings'}
Beverly = {'Name':'Beverly Dsilva','Email':'bevd#hotmail.com','Deposit':23000,'Accno':'CA432178','Acctype':'Current'}
def open_shelf(name='shelfile.shl'):
global Accounts
Accounts = shelve.open(name) #Accounts = {}
Accounts['Beverly']= Beverly
Accounts['Victor']= Victor
def close_shelf():
Accounts.close()
I am able to append values to the shelve but unable to modify the values.
I have defined a function Deposit() from which I would like to modify the data present in the shelve.But it gives me the following error:
Traceback (most recent call last):
File "./functest.py", line 16, in <module>
Deposit()
File "/home/pavitar/Software-Development/Python/Banking/Snippets/Depositfunc.py", line 18, in Deposit
for key in Accounts:
TypeError: 'int' object is not iterable
Here is my Function:
#!/usr/bin/python
import os #This module is imported so as to use clear function in the while-loop
from DB import * #Imports the data from database DB.py
def Deposit():
while True:
os.system("clear") #Clears the screen once the loop is re-invoked
input = raw_input('\nEnter the A/c type: ')
flag=0
for key in Accounts:
if Accounts[key]['Acctype'].find(input) != -1:
amt = input('\nAmount of Deposit: ')
flag+=1
Accounts[key]['Deposit'] += amt
if flag == 0:
print "NO such Account!"
if __name__ == '__main__':
open_shelf()
Deposit()
close_shelf()
I'm new to Python.Please help.Correct me if I'm wrong.I need someone to give a little bit of explanation as to the functioning of this code.I'm confused.
Firstly, don't use a global for Accounts, rather pass it back and forth. Using the global caused your error. Like this:
def open_shelf(name='shelfile.shl'):
Accounts = shelve.open(name) #Accounts = {}
...
return Accounts
def close_shelf(Accounts):
Accounts.close()
def Deposit(Accounts):
...
if __name__ == '__main__':
Accounts = open_shelf()
Deposit(Accounts)
close_shelf(Accounts)
Secondly, don't redefine built-in functions. In Deposit(), you assign the result of raw_input to a variable named input:
input = raw_input('\nEnter the A/c type: ')
Four lines later, you try to use the built-in input function:
amt = input('\nAmount of Deposit: ')
But that won't work because input has been redefined!
Thirdly, when iterating over shelved items, follow the pattern of 1) grab shelved item, 2) mutate item, 3) write mutated item back to shelf. Like so:
for key, acct in Accounts.iteritems(): # grab a shelved item
if val['Acctype'].find(input) != -1:
amt = input('\nAmount of Deposit: ')
flag+=1
acct['Deposit'] += amt # mutate the item
Accounts[key] = acct # write item back to shelf
(This third bit of advice was tweaked from hughdbrown's answer.)
I think you'd have more luck like this:
for key, val in Accounts.iteritems():
if val['Acctype'].find(input) != -1:
amt = input('\nAmount of Deposit: ')
flag+=1
val['Deposit'] += amt
Accounts[key] = val

Categories

Resources