Python: decimal module Recipe function 'moneyfmt' - python

I'm a beginner to Python and I'm having trouble understanding some of the code in the provided moneyfmt function in the Recipes section of the Python Library of the decimal module
decimal Recipes
def moneyfmt(value, places=2, curr='', sep=',', dp='.',
pos='', neg='-', trailneg=''):
"""Convert Decimal to a money formatted string.
places: required number of places after the decimal point
curr: optional currency symbol before the sign (may be blank)
sep: optional grouping separator (comma, period, space, or blank)
dp: decimal point indicator (comma or period)
only specify as blank when places is zero
pos: optional sign for positive numbers: '+', space or blank
neg: optional sign for negative numbers: '-', '(', space or blank
trailneg:optional trailing minus indicator: '-', ')', space or blank
>>> d = Decimal('-1234567.8901')
>>> moneyfmt(d, curr='$')
'-$1,234,567.89'
>>> moneyfmt(d, places=0, sep='.', dp='', neg='', trailneg='-')
'1.234.568-'
>>> moneyfmt(d, curr='$', neg='(', trailneg=')')
'($1,234,567.89)'
>>> moneyfmt(Decimal(123456789), sep=' ')
'123 456 789.00'
>>> moneyfmt(Decimal('-0.02'), neg='<', trailneg='>')
'<0.02>'
"""
q = Decimal(10) ** -places # 2 places --> '0.01'
sign, digits, exp = value.quantize(q).as_tuple()
result = []
digits = list(map(str, digits))
build, next = result.append, digits.pop
if sign:
build(trailneg)
for i in range(places):
build(next() if digits else '0')
if places:
build(dp)
if not digits:
build('0')
i = 0
while digits:
build(next())
i += 1
if i == 3 and digits:
i = 0
build(sep)
build(curr)
build(neg if sign else pos)
return ''.join(reversed(result))
The part I can't follow is:
build, next = result.append, digits.pop
if sign:
build(trailneg)
for i in range(places):
build(next() if digits else '0')
if places:
build(dp)
if not digits:
build('0')
i = 0
while digits:
build(next())
i += 1
if i == 3 and digits:
i = 0
build(sep)
build(curr)
build(neg if sign else pos)
I've looked up the next() method, but I don't understand how it's implemented here. I haven't been able to find 'build' listed as a Python method, or function anywhere. I think it's a variable, but if it IS a variable, I really don't get how it's being used here.
Can someone walk me through the code here?

basically the build and next are variables that refer to methods manipulating with the "result" variable
Example
x = [1, 2, 3]
add = x.append
add(4)
x printed should look like this: "[1, 2, 3, 4]"
same with the "next", but the next is actually "result.pop"
if you will call, for example, just "print" to python3 console, you can notice
"built-in function print", that means it is inbuild function, but that doesn't matter at all, for now, if you write your function and do the same, it will print name of that function and address in memory that contains executable function, with parentheses you say to python that you want to execute this function, back to print, if you will store the print as a variable, you can use the variable as an alias for print.
new_print = print
new_print('foo')
This is very useful when you have a function, that will call another function, this is used in many libraries such as GUI development PyQt and many others. You can do it by yourself:
def foo(number, function, arg):
if number > 5:
function(arg)
if you will call the foo(1, print, 'bar')
it will print "bar"
Simple enough, or still complicated?
Anything else you'd like to understand?
And happy pythoning!

Related

How do i make the program print specific letters in this specific format i give to it?

so i need to code a program which, for example if given the input 3[a]2[b], prints "aaabb" or when given 3[ab]2[c],prints "abababcc"(basicly prints that amount of that letter in the given order). i tried to use a for loop to iterate the first given input and then detect "[" letters in it so it'll know that to repeatedly print but i don't know how i can make it also understand where that string ends
also this is where i could get it to,which probably isnt too useful:
string=input()
string=string[::-1]
bulundu=6
for i in string:
if i!="]":
if i!="[":
lst.append(i)
if i=="[":
break
The approach I took is to remove the brackets, split the items into a list, then walk the list, and if the item is a number, add that many repeats of the next item to the result for output:
import re
data = "3[a]2[b]"
# Remove brackets and convert to a list
data = re.sub(r'[\[\]]', ' ', data).split()
result = []
for i, item in enumerate(data):
# If item is a number, print that many of the next item
if item.isdigit():
result.append(data[i+1] * int(item))
print(''.join(result))
# aaabb
A different approach, inspired by Subbu's use of re.findall. This approach finds all 'pairs' of numbers and letters using match groups, then multiplies them to produce the required text:
import re
data = "3[a]2[b]"
matches = re.findall('(\d+)\[([a-zA-Z]+)\]',data)
# [(3, 'a'), (2, 'b')]
for x in matches:
print(x[1] * int(x[0]), end='')
#aaabb
Lenghty and documented version using NO regex but simple string and list manipulation:
first split the input into parts that are numbers and texts
then recombinate them again
I opted to document with inline comments
This could be done like so:
# testcases are tuples of input and correct result
testcases = [ ("3[a]2[b]","aaabb"),
("3[ab]2[c]","abababcc"),
("5[12]6[c]","1212121212cccccc"),
("22[a]","a"*22)]
# now we use our algo for all those testcases
for inp,res in testcases:
split_inp = [] # list that takes the splitted values of the input
num = 0 # accumulator variable for more-then-1-digit numbers
in_text = False # bool that tells us if we are currently collecting letters
# go over all letters : O(n)
for c in inp:
# when a [ is reached our num is complete and we need to store it
# we collect all further letters until next ] in a list that we
# add at the end of your split_inp
if c == "[":
split_inp.append(num) # add the completed number
num = 0 # and reset it to 0
in_text = True # now in text
split_inp.append([]) # add a list to collect letters
# done collecting letters
elif c == "]":
in_text = False # no longer collecting, convert letters
split_inp[-1] = ''.join(split_inp[-1]) # to text
# between [ and ] ... simply add letter to list at end
elif in_text:
split_inp[-1].append(c) # add letter
# currently collecting numbers
else:
num *= 10 # increase current number by factor 10
num += int(c) # add newest number
print(repr(inp), split_inp, sep="\n") # debugging output for parsing part
# now we need to build the string from our parsed data
amount = 0
result = [] # intermediate list to join ['aaa','bb']
# iterate the list, if int remember it, it text, build composite
for part in split_inp:
if isinstance(part, int):
amount = part
else:
result.append(part*amount)
# join the parts
result = ''.join(result)
# check if all worked out
if result == res:
print("CORRECT: ", result + "\n")
else:
print (f"INCORRECT: should be '{res}' but is '{result}'\n")
Result:
'3[a]2[b]'
[3, 'a', 2, 'b']
CORRECT: aaabb
'3[ab]2[c]'
[3, 'ab', 2, 'c']
CORRECT: abababcc
'5[12]6[c]'
[5, '12', 6, 'c']
CORRECT: 1212121212cccccc
'22[a]'
[22, 'a']
CORRECT: aaaaaaaaaaaaaaaaaaaaaa
This will also handle cases of '5[12]' wich some of the other solutions wont.
You can capture both the number of repetitions n and the pattern to repeat v in one go using the described pattern. This essentially matches any sequence of digits - which is the first group we need to capture, reason why \d+ is between brackets (..) - followed by a [, followed by anything - this anything is the second pattern of interest, hence it is between backets (...) - which is then followed by a ].
findall will find all these matches in the passed line, then the first match - the number - will be cast to an int and used as a multiplier for the string pattern. The list of int(n) * v is then joined with an empty space. Malformed patterns may throw exceptions or return nothing.
Anyway, in code:
import re
pattern = re.compile("(\d+)\[(.*?)\]")
def func(x): return "".join([v*int(n) for n,v in pattern.findall(x)])
print(func("3[a]2[b]"))
print(func("3[ab]2[c]"))
OUTPUT
aaabb
abababcc
FOLLOW UP
Another solution which achieves the same result, without using regular expression (ok, not nice at all, I get it...):
def func(s): return "".join([int(x[0])*x[1] for x in map(lambda x:x.split("["), s.split("]")) if len(x) == 2])
I am not much more than a beginner and looking at the other answers, I thought understanding regex might be a challenge for a new contributor such as yourself since I myself haven't really dealt with regex.
The beginner friendly way to do this might be to loop through the input string and use string functions like isnumeric() and isalpha()
data = "3[a]2[b]"
chars = []
nums = []
substrings = []
for i, char in enumerate(data):
if char.isnumeric():
nums.append(char)
if char.isalpha():
chars.append(char)
for i, char in enumerate(chars):
substrings.append(char * int(nums[i]))
string = "".join(substrings)
print(string)
OUTPUT:
aaabb
And on trying different values for data:
data = "0[a]2[b]3[p]"
OUTPUT bbppp
data = "1[a]1[a]2[a]"
OUTPUT aaaa
NOTE: In case you're not familiar with the above functions, they are string functions, which are fairly self-explanatory. They are used as <your_string_here>.isalpha() which returns true if and only if the string is an alphabet (whitespace, numerics, and symbols return false
And, similarly for isnumeric()
For example,
"]".isnumeric() and "]".isalpha() return False
"a".isalpha() returns True
IF YOU NEED ANY CLARIFICATION ON A FUNCTION USED, PLEASE DO NOT HESITATE TO LEAVE A COMMENT

How to evaluate an equation with a function?

I'm trying to create a function which will solve for some numeric computation – which is given as a string.
Example:
def calculate(expression):
# Solve the expression below
return
# Result should be 19
calculate("5+8-3+9")
I have tried using .split() but got stuck.
For a problem like this we can try tackling it by using this, a string calculator.
'''
Calculates a string of characters only for addition and subtraction
with parentheses. This calculator utilizes the stack method.
'''
import re # imports regular expression library for usage
def calculate(s: str) -> int:
s = re.sub(r'[A-Za-z\s\t]+', '', s)
res = 0
num = 0
sign = 1
stack = []
for ss in s:
# checks if each element is a digit
if ss.isdigit():
num = 10 * num + int(ss)
# if not a digit, checks if its + or - sign
elif ss in ["-", "+"]:
res = res + sign * num
num = 0
sign = [-1, 1][ss == "+"]
'''
sign = [-1, 1][ss=="+"] is the same as:
# int(True) = 1, int(False) = 0. Hence,
if ss == "+":
sign = 1
else:
sign = -1
'''
return res + num * sign
s = input("Enter your string: ")
# OR if you'd like, can uncomment this line below and comment the line above.
# s = "5+8-3+9" # As an expression in a string
print(calculate(s))
I would suggest breaking the question down to its numbers and operators.
Also, I've made the assumption that only whole numbers will be used – and only addition and subtraction.
def calculate(expression):
# Get all components
components = re.findall("[+-]{1}[0-9]*|^[0-9]*",expression)
# get each number with its positive or negative operator
operators = re.compile("[-+]")
# Iterate and add to a list
all_nums = []
for x in components:
# get the number
n = int(re.sub(operators,"",x))
# For all terms after the first
if operators.search(x):
op = operators.search(x).group()
if op=="+":
n = n
elif op=="-":
n=-n
# Save the number
all_nums.append(n)
# Finally, add them up
return sum(all_nums)
x = "5+8-3+9"
calculate(x)
# returns 19
First of all, it's okay to be a beginner - I was in your exact shoes just a few years ago!
I'm going to attempt to provide an elementary/beginner approach to solving this problem, with just the basics.
So first we want to determine what the limits of our function input will be. For this, I'll assume we only accept mathematical expressions with basic addition/subtraction operators.
import re
def calculate(expression: str) -> int:
if not re.match("^[0-9\+-]*$", expression):
return None
For this you'll see I opted for regex, which is a slightly more advanced concept, but think about it like a validity check for expression. Basically, the pattern I wrote checks that there is a fully qualified string that has only integers, plus sign, and minus sign. If you want to learn more about the expression ^[0-9\+-]*$, I highly recommend https://regexr.com/.
For our purposes and understanding though, these test cases should suffice:
>>> re.match("^[0-9\+-]*$", "abcs")
>>> re.match("^[0-9\+-]*$", "1+2")
<re.Match object; span=(0, 3), match='1+2'>
>>> re.match("^[0-9\+-]*$", "1+2/3")
>>>
Now that we have verified our expression, we can get to work on calculating the final value.
Let's try your idea with str.split()! It won't be entirely straightforward because split by definition splits a string up according to a delimiter(s) but discards them in the output. Fear not, because there's another way! The re package I imported earlier can come into handy. So the re library comes with a handy function, split!
By using capture groups for our separator, we are able to split and keep our separators.
>>> re.split("(\d+)", "1+393958-3")
['', '1', '+', '393958', '-', '3', '']
So, with this up our sleeve...
import re
def calculate(expression: str) -> int:
if not re.match("^[0-9\+-]*$", expression):
return None
expression_arr = re.split("(\d+)", expression)[1:-1]
while len(expression_arr) > 1:
# TODO stuff
return int(expression[0])
We can now move onto our while loop. It stands to reason that as long as the array has more than one item, there is some sort of operation left to do.
import re
def calculate(expression: str) -> int:
if not re.match("^[0-9\+-]*$", expression):
return None
expression_arr = re.split("(\d+)", expression)[1:-1]
while len(expression_arr) > 1:
if expression_arr[1] == "+":
eval = int(expression_arr[0]) + int(expression_arr[2])
if expression_arr[1] == "-":
eval = int(expression_arr[0]) - int(expression_arr[2])
del expression_arr[:3]
expression_arr.insert(0, eval)
return int(expression_arr[0])
It's pretty straightforward from there - we check the next operator (which always has to be at expression_arr[1]) and either add or subtract, and make the corresponding changes to expression_arr.
We can verify that it passes the test case you provided. (I added some logging to help with visualization)
>>> calculate("5+8-3+9")
['5', '+', '8', '-', '3', '+', '9']
[13, '-', '3', '+', '9']
[10, '+', '9']
[19]
19

I am getting the output for the program but not in the required way?

Write a Python function that accepts a string as an input.
The function must return the sum of the digits 0-9 that appear in the string, ignoring all other characters.Return 0 if there are no digits in the string.
my code:
user_string = raw_input("enter the string: ")
new_user_string = list(user_string)
addition_list = []
for s in new_user_string:
if ( not s.isdigit()):
combine_string = "".join(new_user_string)
print ( combine_string)
else:
if ( s.isdigit()):
addition_list.append(s)
test = "".join(addition_list)
output = sum(map(int,test))
print ( output )
the output should be:
Enter a string: aa11b33
8
my output:
enter the string: aa11b33
aa11b33
aa11b33
1
2
aa11b33
5
8
This look suspiciously like homework...
getsum = lambda word: sum(int(n) for n in word if n.isdigit())
getsum('aa11b33')
Out[3]: 8
getsum('aa')
Out[4]: 0
An explanation of how this works piece-by-piece:
The function n.isdigit() returns True if n is composed only of one or more digits, false otherwise. (Documentation)
The syntax for n in word will loop over each item in the iterable word. Since word is a string, python considers each character to be an individual item.
The operation sum(int(n) for n in word...) casts each character to an int and takes the sum of all of them, while the suffix if n.isdigit() filters out all non-digit characters. Thus the end result will just take the sum of all the individual digit characters in the string word.
The syntax lambda x: some expression using x constructs an anonymous function which takes some value x as its parameter, and returns the value of the expression after the colon. To give this function a name we can just put it on the right-hand-side of an assignment statement. Then we can call it like a normal function. Usually it's better to use a normal def getsum(x) kind of function definition, however lambda expressions are sometimes useful for if you have a one-off kind of function you just need to use as a parameter to a function. In general in python it's better if you can find a way to avoid them, as they're not considered very readable.
Here is a complete example:
def sumword(word):
return sum( int(n) for n in word if n.isdigit() )
word = raw_input("word please: ")
print(sumword(word))
It should be
user_string = raw_input("enter the string: ")
new_user_string = list(user_string)
addition_list = []
for s in new_user_string:
if ( not s.isdigit()):
combine_string = "".join(new_user_string)
else:
if ( s.isdigit()):
addition_list.append(s)
test = "".join(addition_list)
output = sum(map(int,addition_list))
print output
You were getting the output you were for two reasons.
In the if statement, you were telling it to print the string that was originally inputted when it came across a non-digit. This makes perfect sense as you look at your output - the string is printed when it sees a, the string is printed when it sees the second a, and the string is not printed, not printed (for the ones) and then the string is printed for the last time with the b.
You were printing the output as the for loop incremented through the list, meaning it printed the total each time. Moving the output variable and print statement outside the for loop fixed the problem.
Python cares about indentations:
user_string = raw_input("enter the string: ")
new_user_string = list(user_string)
addition_list = []
for s in new_user_string:
if ( not s.isdigit()):
combine_string = "".join(new_user_string)
#print ( combine_string)
else:
if ( s.isdigit()):
addition_list.append(s)
test = "".join(addition_list)
output = sum(map(int,test))
print ( output ) #<------ change here
Also got rid of your inside print statement since it's not your output.

Replace multiple characters in a string with one from a generator

I'm looking for a way to take a string that looks like the following:
(a,1),(b,1),(a,1),(b,5),(a,1),(b,2),(a,1),(b,1),(a,2),(b,6),(a,2)
And replace the first "a" with an even number, the second with the next
up even number, and so on for however long the string is. Then I'd like to take the first "b" and assign it an odd number, then the next "b" gets the
next highest odd number, and so on for however long the string is. I'm
working primarily in Python 2.7, but would be willing to look into other languages if a solution exists in that.
The following regular expression substitution should work:
import re
def odd_even(x):
global a,b
if x.group(1) == 'a':
a += 2
return str(a)
else:
b += 2
return str(b)
a = 0
b = -1
source = "(a,1),(b,1),(a,1),(b,5),(a,1),(b,2),(a,1),(b,1),(a,2),(b,6),(a,2)"
print re.sub("([ab])", odd_even, source)
This prints:
(2,1),(1,1),(4,1),(3,5),(6,1),(5,2),(8,1),(7,1),(10,2),(9,6),(12,2)
even = 2
while "a" in string:
string = string.replace("a", str(even), 1)
even += 2
odd = 1
while "b" in string:
string = string.replace("b", str(odd), 1)
odd += 2

Replace numbers in string by respective result of a substraction

I have a string like this:
"foo 15 bar -2hello 4 asdf+2"
I'd like to get:
"foo 14 bar -3hello 3 asdf+1"
I would like to replace every number (sequence of digits as signed base-10 integers) with the result of a subtraction executed on each of them, one for each number.
I've written a ~50 LOC function that iterates on characters, separating signs, digits and other text, applying the function and recombining the parts. Although it has one issue my intent with the question is not to review it. Instead I'm trying to ask, what is the pythonic way to solve this, is there an easier way?
For reference, here is my function with the known issue, but my intention is not asking for a review but finding the most pythonic way instead.
edit to answer the wise comment of Janne Karila:
preferred: retain sign if given: +2 should become +1
preferred: zero has no sign: +1 should become 0
preferred: no spaces: asdf - 4 becomes asdf - 3
required: only one sign: -+-2 becomes -+-3
edit on popular demand here is my buggy code :)
DISCLAIMER: Please note I'm not interested in fixing this code. I'm asking if there is a better approach than something like mine.
def apply_to_digits(some_str,handler):
sign = "+"
started = 0
number = []
tmp = []
result = []
for idx,char in enumerate(some_str):
if started:
if not char.isdigit():
if number:
ss = sign + "".join(number)
rewritten = str(handler(int(ss)))
result.append(rewritten)
elif tmp:
result.append("".join(tmp))
number = []
tmp = []
sign = "+"
started = 0
# char will be dealt later
else:
number.append(char)
continue
if char in "-+":
sign = char
started = 1
if tmp:
result.append("".join(tmp))
tmp = []
tmp.append(char)
continue
elif char.isdigit():
started = 1
if tmp:
result.append("".join(tmp))
tmp = []
number.append(char)
else:
tmp.append(char)
if number:
ss = sign + "".join(number)
rewritten = str(handler(int(ss)))
result.append(rewritten)
if tmp:
result.append("".join(tmp)), tmp
return "".join(result)
#
DISCLAIMER: Please note I'm not interested in fixing this code. I'm asking if there is a better approach than something like mine.
You could try using regex, and using re.sub:
>>> pattern = "(-?\d+)|(\+1)"
>>> def sub_one(match):
return str(int(match.group(0)) - 1)
>>> text = "foo 15 bar -2hello 4 asdf+2"
>>> re.sub(pattern, sub_one, text)
'foo 14 bar -3hello 3 asdf+1'
The regex (-?\d+)|(\+1) will either capture an optional - sign and one or more digits, OR the literal sequence +1. That way, the regex will make sure that all of your requirements when converting digits work properly.
The regex (-?\d+) by itself does the right thing most of the time, but the (\+1) exists to make sure that the string +1 always converts to zero, without a sign. If you change your mind, and want +1 to convert to +0, then you can just use only the first part of the regex: (-?d+).
You could probably compress this all into a one-liner if you wanted:
def replace_digits(text):
return re.sub("(-?\d+)|(\+1)", lambda m: str(int(m.group(0)) - 1), text)

Categories

Resources