Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

A useful, but painful, one-liner to edit money amounts

2 views
Skip to first unread message

John Nagle

unread,
Aug 5, 2010, 12:33:31 AM8/5/10
to
There's got to be a better way to do this:


def editmoney(n) :
return((",".join(reduce(lambda lst, item : (lst + [item]) if
item else lst,
re.split(r'(\d\d\d)',str(n)[::-1]),[])))[::-1])


>>> editmoney(0)
'0'
>>> editmoney(13535)
'13,535'
>>> editmoney(-14535)
'-14,535'
>>> editmoney(123456)
'123,456'
>>> editmoney(1234567890)
'1,234,567,890'
>>> editmoney(-1234)
'-1,234'

The basic idea here is that we want to split the string of digits
into groups of 3 digits, aligned at the right. Because regular
expressions are right to left, we have to reverse the string to
do that, then reverse again at the end. s[::-1} reverses an
interable.

"split" with a capturing group introduces empty strings into the
list. Hence the "reduce" and lambda to get rid of them.

Any better ideas?

(Yes, I know there's a built-in feature for this scheduled for
Python 2.7.)

John Nagle


Paul Rubin

unread,
Aug 5, 2010, 2:03:02 AM8/5/10
to
John Nagle <na...@animats.com> writes:
> def editmoney(n) :
> return((",".join(reduce(lambda lst, item : (lst + [item]) if
> item else lst,
> re.split(r'(\d\d\d)',str(n)[::-1]),[])))[::-1])

Too obscure. I usually use something like this:

def editmoney(n):
if n < 0: return '-' + editmoney(-n)
if n >= 1000:
return editmoney(n // 1000) + ',%03d'% (n % 1000)
return '%d'% n

Steven D'Aprano

unread,
Aug 5, 2010, 2:20:58 AM8/5/10
to
On Wed, 04 Aug 2010 21:33:31 -0700, John Nagle wrote:

> There's got to be a better way to do this:
>
>
> def editmoney(n) :
> return((",".join(reduce(lambda lst, item : (lst + [item]) if
> item else lst,
> re.split(r'(\d\d\d)',str(n)[::-1]),[])))[::-1])

What does the name "editmoney" mean?

Why the obfuscated one-liner? It's not like you're using it in-line,
you're putting it in a function, so who cares if it's one line or twenty?

def group_digits(n, size=3, sep=','):
"""Group int n in groups of size digits separated by sep."""
s = str(n)
m = len(s) % size
head = s[0:m]
tail = s[m:]
groups = [tail[i*size:(i+1)*size] for i in range(len(tail)//size)]
tail = sep.join(groups)
if head and tail:
return head + sep + tail
elif tail:
return tail
else:
return head


>>> group_digits(0)
'0'
>>> group_digits(1234567890)
'1,234,567,890'
>>> group_digits(1234567890, 4, ';')
'12;3456;7890'


Additional error checking, a better docstring, and extending it to
support negative numbers is left as an exercise.

--
Steven

Peter Otten

unread,
Aug 5, 2010, 2:30:09 AM8/5/10
to
John Nagle wrote:


>>> locale.setlocale(locale.LC_ALL, ("en_US", "UTF-8"))
'en_US.UTF8'
>>> print locale.currency(13535, grouping=True)
$13,535.00
>>> print locale.format("%d", 13535, grouping=True)
13,535

>>> locale.setlocale(locale.LC_ALL, "")
'de_DE.UTF-8'
>>> print locale.currency(13535, grouping=True)
13.535,00 €
>>> print locale.format("%d", 13535, grouping=True)
13.535

Peter

geremy condra

unread,
Aug 5, 2010, 3:22:57 AM8/5/10
to Peter Otten, pytho...@python.org

I had literally no idea this existed. Thanks.

Geremy Condra

Steven D'Aprano

unread,
Aug 5, 2010, 4:29:54 AM8/5/10
to
On Thu, 05 Aug 2010 00:22:57 -0700, geremy condra wrote:

>>>>> locale.setlocale(locale.LC_ALL, "")
>> 'de_DE.UTF-8'
>>>>> print locale.currency(13535, grouping=True)
>> 13.535,00 €
>>>>> print locale.format("%d", 13535, grouping=True)
>> 13.535
>>
>> Peter
>
> I had literally no idea this existed. Thanks.

I knew it existed, but completely forgot about it.

Thanks also Peter.


--
Steven

Chris Withers

unread,
Aug 5, 2010, 7:06:17 AM8/5/10
to Peter Otten, pytho...@python.org
Peter Otten wrote:
>>>> locale.setlocale(locale.LC_ALL, ("en_US", "UTF-8"))
> 'en_US.UTF8'
>>>> print locale.currency(13535, grouping=True)
> $13,535.00

Okay, so if I'm writing a wsgi app, and I want to format depending on
the choices of the currently logged in users, what would you recommend?

I can't do setlocale, since that would affect all users, and in a
mult-threaded environment that would be bad.

Does that mean the whole locale package is useless to all web-app builders?

Chris

DarkBlue

unread,
Aug 5, 2010, 7:56:38 AM8/5/10
to

from re import *

class editmoney(float):
def __init__(self,mymoney):
self.mymoney = mymoney
def __str__(self):
temp = "%.2f" % self.mymoney
profile = compile(r"(\d)(\d\d\d[.,])")
while 1:
temp, count = subn(profile,r"\1,\2",temp)
if not count: break
return temp

Peter Otten

unread,
Aug 5, 2010, 8:27:44 AM8/5/10
to

John Posner

unread,
Aug 5, 2010, 10:01:59 AM8/5/10
to John Nagle
On 8/5/2010 12:33 AM, John Nagle wrote:
> There's got to be a better way to do this:
>
>
> def editmoney(n) :
> return((",".join(reduce(lambda lst, item : (lst + [item]) if
> item else lst,
> re.split(r'(\d\d\d)',str(n)[::-1]),[])))[::-1])
>

Here's a more elegant variant, using regexp lookahead:

def thous_format(integer_string):
"""
add comma thousands separator(s) to an integer-valued string
"""
return re.sub(r'(\d{3})(?=\d)', r'\1,', integer_string[::-1])[::-1]

I *thought* that I had found this on python-list on or about July 5, but
I didn't find the thread after a search through the archives.

-John

MRAB

unread,
Aug 5, 2010, 12:36:50 PM8/5/10
to pytho...@python.org
You don't need to reverse the string:

def thous_format(integer_string):
"""
add comma thousands separator(s) to an integer-valued string
"""

return re.sub(r"(?<=\d)(?=(?:\d\d\d)+$)", ",", integer_string)

John Posner

unread,
Aug 5, 2010, 2:31:40 PM8/5/10
to MRAB, pytho...@python.org
On 8/5/2010 12:36 PM, MRAB wrote:

> You don't need to reverse the string:
>
> def thous_format(integer_string):
> """
> add comma thousands separator(s) to an integer-valued string
> """
> return re.sub(r"(?<=\d)(?=(?:\d\d\d)+$)", ",", integer_string)

Nice! My first encounter with a look-behind! It appears that a slight
simplification is possible:

return re.sub(r"(?<=\d)(?=(\d\d\d)+$)", ",", integer_string)

There no harm in putting \d\d\d into a group, though the group is never
used.

-John

John Posner

unread,
Aug 5, 2010, 2:31:40 PM8/5/10
to MRAB, pytho...@python.org
On 8/5/2010 12:36 PM, MRAB wrote:

> You don't need to reverse the string:
>
> def thous_format(integer_string):
> """
> add comma thousands separator(s) to an integer-valued string
> """
> return re.sub(r"(?<=\d)(?=(?:\d\d\d)+$)", ",", integer_string)

Nice! My first encounter with a look-behind! It appears that a slight

Peter Otten

unread,
Aug 5, 2010, 5:13:10 PM8/5/10
to
Steven D'Aprano wrote:

You're welcome!

Rebel Lion

unread,
Aug 7, 2010, 5:53:04 AM8/7/10
to

In [1]: '{:,}'.format(-12345678)
Out[1]: '-12,345,678'

Chris Withers

unread,
Aug 6, 2010, 3:06:00 PM8/6/10
to DarkBlue, pytho...@python.org
DarkBlue wrote:
> On Aug 5, 7:06 pm, Chris Withers <ch...@simplistix.co.uk> wrote:
>> Peter Otten wrote:
>>>>>> locale.setlocale(locale.LC_ALL, ("en_US", "UTF-8"))
>>> 'en_US.UTF8'
>>>>>> print locale.currency(13535, grouping=True)
>>> $13,535.00
>> Okay, so if I'm writing a wsgi app, and I want to format depending on
>> the choices of the currently logged in users, what would you recommend?
>>
>> I can't do setlocale, since that would affect all users, and in a
>> mult-threaded environment that would be bad.
>>
>> Does that mean the whole locale package is useless to all web-app builders?
>>
>> Chris
>
> from re import *
>
> class editmoney(float):
> def __init__(self,mymoney):

What does this have to do with my question about locales in
multi-threaded environments?

Chris

--
Simplistix - Content Management, Batch Processing & Python Consulting
- http://www.simplistix.co.uk

0 new messages