I have a simple script that takes a list containing 3 columns of data. The second column of data contains currency values with a leading dollar sign. I have stripped away the dollar sign from the second column, now I need to add up the values. I'm getting a "decimal.Decimal is not iterable" error. Here is the code:
from decimal import Decimal
def main():
total = 0.0
try:
infile = open('list.txt', 'r')
for i in infile:
parts = i.split()
if len(parts) > 1:
dollar_dec = Decimal((parts[1]).strip('$'))
total = sum(dollar_dec)
print (total)
infile.close()
except Exception as err:
print(err)
main()
Say, you have the following file content:
content = """\
one $1.50
two $3.00
three $4.50"""
You can use the in-place operator += to calculate the total:
from decimal import Decimal
import io
total = Decimal(0)
with io.StringIO(content) as fd:
for line in fd:
parts = line.strip().split()
if len(parts) > 1:
dollard_dec = Decimal(parts[1].strip("$"))
total += dollard_dec
print(total)
Her, you get: 9.00
You can also use sum() on a iterable (here a comprehension list):
with io.StringIO(content) as fd:
total = sum(Decimal(line.strip().split()[1].strip("$"))
for line in fd)
print(total)
Yes, you get 9.00 too!
total = sum(dollar_dec)
sum() takes an iterable (a list, for example) and adds up all the values. You are passing it a single number, which is an error. You probably want
total = Decimal('0.0')
...
total += dollar_dec
Which will keep a running total.
(edit- total must be a Decimal for you to add Decimals to it)
sum() takes an iterable. Just change your code to total += dollar_dec
Related
All I have a text file formatted like below which I am bringing into Python:
hammer#9.95
saw#20.15
shovel#35.40
Ultimately I want to develop a dynamic query that allows me to remove the '#' symbol and replace with a '$' symbol, and then add up the values within the text file/count the number of items within. I came up with this through some trial and error, but it isn't dynamic to handle changes in the text file:
# display header line for items list
print('{0: <10}'.format('Item'), '{0: >17}'.format('Cost'), sep = '' )
# add your remaining code below
with open('invoice.txt','rt') as infile:
for line in infile:
print("{:<21} {}".format(line.strip().split('#')[0],"$"+line.strip().split("#")[1]))
print(' ')
str1 = 'Total cost\t' +' ' + '$65.50'
print(str1)
str2 = 'Number of tools\t' + ' ' +'3'
print(str2)
Any suggestions? Thanks ahead of time for reading.
You can do it the following way:
d = ['hammer#9.95', 'saw#20.15', 'shovel#35.40']
## replace hash
values = []
items = set()
for line in d:
line = line.replace('#', '$')
values.append(line.split('$')[1])
items.add(line.split('$')[0])
## sum values
sum(map(lambda x: float(x), values))
65.5
## count items
len(items)
3
Explanation:
To count items, we've used a set to get unique count. If you want all, use a list instead.
We've calculated sum by extracting the numbers from list by splitting on dollar sign.
prices = []
with open(...) as infile:
for line in infile.readlines()
price = line.split('#')[-1]
prices.append(float(price))
result = sum(prices)
What about:
items = {}
with open("temp.txt") as f:
for line in f:
item,cost = line.split('#')
cost = float(cost)
items[item] = cost
Now, you have a dictionary, keyed by item "name" (so they need to be unique in your file, otherwise a dictionary isn't the best structure here) and each value is a float corresponding to the parsed cost.
# Print items and cost
print(items.items())
#> dict_items([('hammer', 9.95), ('saw', 20.15), ('shovel', 35.4)])
# Print Number of Items
print(len(items))
#> 3
# Print Total Cost (unformatted)
print(sum(items.values()))
#> 65.5
# Print Total Cost (formatted)
print("$%.02f" % sum(items.values()))
#> $65.50
There are some corner cases you may want to look at to make this solution more robust. For example if the item "name" includes a # sign (i.e. there is more than one # per line), the values aren't properly formatted to be parsed by float, etc.
You can use:
total_price, total_products = 0, 0
for line in [open('invoice.txt').read().split("\n")]:
total_price += float(line.split("#")[1]); total_products += 1
print("Total Price\n${}".format(total_price))
print("Number of tools\n{}".format(total_products))
Total Price
$65.5
Number of tools
3
We have to cast the price (line.split("#")[1]), which is a string, to a float, otherwise we get a Type Error when we try to add it to total_price.
float(line.split("#")[1])
Since it is long due that i should refresh my Python skills i had some fun with your question and came up with a parser class:
import re
from contextlib import contextmanager
class Parser(object):
def __init__(self, file_path, regex):
self.file_path = file_path
self.pattern = re.compile(regex, flags=re.LOCALE | re.IGNORECASE | re.UNICODE)
self.values = []
self.parse()
#contextmanager
def read_lines(self):
try:
with open(self.file_path, "r", encoding="utf-8") as f:
yield f.readlines()
except FileNotFoundError:
print("Couldn't open file: ", self.file_path)
def parse_line(self, line):
try:
return self.pattern.match(line).groupdict()
except AttributeError:
return None
def parse(self):
with self.read_lines() as lines:
self.values = [value for value in map(self.parse_line, lines) if value]
def get_values(self, converters=dict()):
if len(converters) is 0:
return self.values
new_values = []
for value in self.values:
new_value = {}
for key in value:
if key in converters:
new_value[key] = converters[key](value[key])
else:
new_value[key] = value[key]
new_values.append(new_value)
return new_values
This class takes a file path and a regex-like string, which is then compiled to a regex object. On instantiation it reads and parses the contents of the file while ignoring invalid lines (not matching the regex syntax like empty lines).
I also added a get_values method which can apply converters to named groups from the regex, see the example (it converts the named group price of every line into a float value):
parser = Parser(r"fully_qualified_file_path.txt", r".\s*(?P<name>[\w\s]+)\#(?P<price>[\d\.]+)")
total = 0
count = 0
for line in parser.get_values({'price': lambda x: float(x)}):
total += line['price']
count += 1
print('Item: {name}, Price: ${price}'.format(**line))
print()
print('Item count:', count)
print('Total:', "${0}".format(total))
Result
Item: hammer, Price: $9.95
Item: saw, Price: $20.15
Item: shovel, Price: $35.4
Item count: 3
Total: $65.5
But coding fun aside, i suggest you try to get clean csv-like data and handle it properly through the csv class.
I need to read an input file (input.txt) which contains one line of integers (13 34 14 53 56 76) and then compute the sum of the squares of each number.
This is my code:
# define main program function
def main():
print("\nThis is the last function: sum_of_squares")
print("Please include the path if the input file is not in the root directory")
fname = input("Please enter a filename : ")
sum_of_squares(fname)
def sum_of_squares(fname):
infile = open(fname, 'r')
sum2 = 0
for items in infile.readlines():
items = int(items)
sum2 += items**2
print("The sum of the squares is:", sum2)
infile.close()
# execute main program function
main()
If each number is on its own line, it works fine.
But, I can't figure out how to do it when all the numbers are on one line separated by a space. In that case, I receive the error: ValueError: invalid literal for int() with base 10: '13 34 14 53 56 76'
You can use file.read() to get a string and then use str.split to split by whitespace.
You'll need to convert each number from a string to an int first and then use the built in sum function to calculate the sum.
As an aside, you should use the with statement to open and close your file for you:
def sum_of_squares(fname):
with open(fname, 'r') as myFile: # This closes the file for you when you are done
contents = myFile.read()
sumOfSquares = sum(int(i)**2 for i in contents.split())
print("The sum of the squares is: ", sumOfSquares)
Output:
The sum of the squares is: 13242
You are trying to turn a string with spaces in it, into an integer.
What you want to do is use the split method (here, it would be items.split(' '), that will return a list of strings, containing numbers, without any space this time. You will then iterate through this list, convert each element to an int as you are already trying to do.
I believe you will find what to do next. :)
Here is a short code example, with more pythonic methods to achieve what you are trying to do.
# The `with` statement is the proper way to open a file.
# It opens the file, and closes it accordingly when you leave it.
with open('foo.txt', 'r') as file:
# You can directly iterate your lines through the file.
for line in file:
# You want a new sum number for each line.
sum_2 = 0
# Creating your list of numbers from your string.
lineNumbers = line.split(' ')
for number in lineNumbers:
# Casting EACH number that is still a string to an integer...
sum_2 += int(number) ** 2
print 'For this line, the sum of the squares is {}.'.format(sum_2)
You could try splitting your items on space using the split() function.
From the doc: For example, ' 1 2 3 '.split() returns ['1', '2', '3'].
def sum_of_squares(fname):
infile = open(fname, 'r')
sum2 = 0
for items in infile.readlines():
sum2 = sum(int(i)**2 for i in items.split())
print("The sum of the squares is:", sum2)
infile.close()
Just keep it really simple, no need for anything complicated. Here is a commented step by step solution:
def sum_of_squares(filename):
# create a summing variable
sum_squares = 0
# open file
with open(filename) as file:
# loop over each line in file
for line in file.readlines():
# create a list of strings splitted by whitespace
numbers = line.split()
# loop over potential numbers
for number in numbers:
# check if string is a number
if number.isdigit():
# add square to accumulated sum
sum_squares += int(number) ** 2
# when we reach here, we're done, and exit the function
return sum_squares
print("The sum of the squares is:", sum_of_squares("numbers.txt"))
Which outputs:
The sum of the squares is: 13242
I am trying to write two functions. One generates random numbers between 1 and 100 with the parameter being the frequency of numbers which saves the information to a file, and another one reads this file and displays the: total sum, average, highest and lowest value. I have managed to get the first function to work however the secound function does not. When running the program I get the TypeError: 'int' object is not iterable due to the "number=int(number)" line in the readNumbers() function, which I don't understand because I thought the number had to be changed from a string to an int in order for equations to work. Any help would be appreciated. Also is there a method of finding the maximum and minimum values without using max() and min(), I would personally like to know.
def generateNumbers(i):
myFile=open("numbers.txt","w")
for n in range(i):
import random
numbers=random.randint(1,100)
numbers=str(numbers)
myFile.write(numbers)
myFile.close()
generateNumbers(400)
def readNumbers():
myFile=open("numbers.txt","r")
for number in myFile:
number=int(number)
total=(sum(number))
average=sum/float(len(number))
while number!=-1:
num_list.append(number)
high= max(num_list)
low= min(num_list)
print(total,average,high,low)
myFile.close()
readNumbers()
Your program has several problems.
Writing the numbers:
You're not separating the numbers in anyway, so your file will contain a very long string of digits (and to nitpick, your numbers is actually random_number)
if you f.ex myFile.write("\n") after myFile.write(random_number) each number
will be separated by a newline.
Now, since separating the numbers with newline, your reading will work, except that you should read them into an array, then do total and average.
ie:
num_list = []
for number in myFile:
if number != "\n": # there is an empty line at the end
num_list.append(int(number))
total = sum(num_list)
average = total / len(num_list)
high = max(num_list)
low = min(num_list)
Note that you don't need the while loop
You could also do the reading with python's list comprehension, and close the file automatically with a context manager
with open("numbers.txt", "r") as f:
num_list = [int(number) for number in f if number != "\n"]
(edited to avoid error on empty line at end)
Kindal is right.
You should do something like:
sum = 0;
count = 0;
for number in myFile:
sum += number
count += 1
average = sum /count
Moreover, I think that you can optimise a bit your code. Read the file and create a list, then you can easily calculate sum, avg, min, max.
with open("my filename.txt", 'r') as myFile:
number_list = list()
for number in myFile:
number = int(number)
if number != -1: #Is it necessary for you?
number_list.append(number)
sum = sum(number_list)
average = sum / len(number_list)
min_val = min(number_list)
max_val = max(number_list)
fname = input("Enter file name: ")
count=0
fh = open(fname)
for line in fh:
if not line.startswith("X-DSPAM-Confidence:") : continue
count=count+1
halo=line.find("0")
gh=line[halo:]
tg=gh.rstrip()
ha=float(tg)
total=0
for value in range(ha):
total=total+value
print total
its like a list of decimal number in file ok
0.1235
0.1236
0.1678
I convert it into float where 'tg' have not an array like a list
ha=float(tg)
total=0
for value in range(ha):
total=total+value
print total
error: start must be an integer
I know it's a mistake of using range what should I use instead of range?
If you want to get a sum of floats, just use the code:
fname = input("Enter file name: ")
count = 0
total = 0
fh = open(fname)
for line in fh:
if not line.startswith("X-DSPAM-Confidence:"): continue
count += 1
halo = line.find("0")
gh = line[halo:]
tg = gh.rstrip()
ha = float(tg)
total += ha
print total
You are passing a float as argument to range, which does not make sense. range returns a list with n elements when n is the only argument of range. For example:
>>> range(3)
[0, 1, 2]
So you can see that range of a float does not make sense.
If I understand your code correctly, I think you want to replace:
for value in range(ha):
total=total+value
By
total += ha
On a separate note, and trying not to be too pedantic, I am pretty impressed by how many principles of PEP 8 your code violates. You may think it's not a big deal, but if you care, I would suggest you read it (https://www.python.org/dev/peps/pep-0008/)
in my data file I have
60,66,88,90,44,90,80,77
all the numbers are in one line
this is my code which does not give me the average of my numbers
inFile3 = open("data2.txt","r")
global gradeVar
gradeVar = len(gradeArray)
global Total
Total = 0
for Numbers in inFile3:
Total = Total + int(Numbers)
inFile3.close()
global averageVar
averageVar = Total/gradeVar
return averageVar
This is the error
Traceback (most recent call last):
File "program.py", line 81, in <module>
main()
File "program.py", line 5, in main
averageVar = Average()
File "program.py", line 39, in Average
Total = Total + int(Numbers)
ValueError: invalid literal for int() with base 10: '60,66,88,90,44,90,80,77\n'
Your problem is here:
for Numbers in inFile3:
Total = Total + int(Numbers)
Numbers in the code above is a list of lines, rather than a list of numbers.
for Line in inFile3:
for number in Line.split(','):
Total = Total + int(number)
should help.
You also have no need to pre-declare variables the way you are in Python. In fact, doing so with global is positively dangerous unless you know what you're doing and why.
Edit:
If you ever have a comma at the end of a line, of a blank value you can change the final line to:
if number.strip():
Total = Total + int(number)
This will ignore any 'empty' number strings that would otherwise throw an error.
This line:
for Numbers in inFile3:
is actually iterating over the lines of the file, not the numbers within each line. You need to iterate over the lines, then for each line split it into numbers, something like this:
for Line in inFile3:
for Number in Line.split(','):
Total = Total + int(Number)
While others have pointed out some of the issues involved in what you are doing, none have pointed out that an average needs not only the sum of all the parts but also the number of all the elements. Hence,
def parseNumberFile(file_name):
for numbers in open(file_name, "r"):
items = numbers.split(',')
yield (len(items), sum(map(int,items)))
which turns it into a generator that you can use as:
total = 0
count = 0
for x,y in parseNumberFile("myData.txt"):
count += x
total += y
average = total/count
Where are you reading the data? You'll need to read it in and then split the String into numbers with something like str.split().
Here is a more pythonic way:
inFile3 = open("data2.txt","r")
grade_list = inFile3.readline()
inFile3.close()
num_list = [int(g) for g in grade_list.split(',')]
average = sum(num_list) / len(num_list)
print average
The error message says it all: '60,66,88,90,44,90,80,77\n', considered as a group, is not a valid integer. You need to consider them one by one. First, strip off the new line, then split by the comma.
Change:
for Numbers in inFile3:
To:
# assumes numbers are all on one line with no spaces between
for Numbers in inFile3.read().strip().split(','):
If I had to rewrite from scratch:
from __future__ import division # this import is not needed in python 3
with open('data2.txt', 'r') as f:
numbers = [int(n) for n in f.read().strip().split(',')]
avg = sum(numbers) / len(numbers)
Change
for Numbers in inFile3:
to
for Numbers in inFile3.strip().split(','):