phone2numeric doesn't convert "Q" or "Z"

3 views
Skip to first unread message

Gabriel Hurley

unread,
Jan 14, 2010, 5:23:39 AM1/14/10
to Django developers
1. Is there a reason Django's phone2numeric method doesn't work for
the letters Q or Z? I realize that those two letters are the ones that
share four letters to a number instead of the standard three, but
that's no reason to leave them out. Most modern phones include the
full alphabet on their keys and it's silly not to let people convert
those two letters.

If there's no reason, I'd be happy to submit a patch since it's such
an easy fix.

2. I was also wondering if there's a reason that the dictionary of
numbers/letters used in that function is in such a seemingly random
order... is there some brilliant logic behind it?

The source for the function I'm referring to is here:

http://code.djangoproject.com/browser/django/trunk/django/utils/text.py#L158

Thanks!

- Gabriel

Łukasz Rekucki

unread,
Jan 14, 2010, 6:14:57 AM1/14/10
to django-d...@googlegroups.com
2010/1/14 Gabriel Hurley <gab...@gmail.com>:

> 2. I was also wondering if there's a reason that the dictionary of
> numbers/letters used in that function is in such a seemingly random
> order... is there some brilliant logic behind it?
Yes, there is. Someone probably copy&pasted it from python's output,
so they're in python's hash order.
If you type into the console:
>>> {'a': 2, 'b': 2, 'c': 2}
{'a': 2, 'c': 2, 'b': 2}
Notice that 'b' and 'c' and reversed. Of course it's a dictionery, so
it doesn't really matter.


> The source for the function I'm referring to is here:
>
> http://code.djangoproject.com/browser/django/trunk/django/utils/text.py#L158
>
> Thanks!
>
>    - Gabriel

--
Łukasz Rekucki

Harro

unread,
Jan 14, 2010, 8:05:42 AM1/14/10
to Django developers
hmm that's indeed weird. The regex excludes those as well
specifically.
The Q and Z should be added or a comment should be added to the code
explaining the reason for leaving them out.

> http://code.djangoproject.com/browser/django/trunk/django/utils/text....
>
> Thanks!
>
>     - Gabriel

Gabriel Hurley

unread,
Jan 14, 2010, 5:33:36 PM1/14/10
to Django developers
I've opened a ticket and submitted a patch that fixes this strange
oversight: http://code.djangoproject.com/ticket/12613

Thanks!
- Gabriel

Andrew Gwozdziewycz

unread,
Jan 15, 2010, 6:41:18 AM1/15/10
to django-d...@googlegroups.com
Why use regular expressions at all for this? A timeit benchmark shows
a greater than 4x speedup with a rangetest in a loop over the string:

def phone2number(str):
chars = {'a': '2', 'b': '2', 'c': '2', 'd': '3', 'e': '3',
'f': '3', 'g': '4', 'h': '4', 'i': '4', 'j': '5', 'k': '5', 'l': '5',
'm': '6', 'n': '6', 'o': '6', 'p': '7', 'q': '7', 'r': '7', 's': '7',
't': '8', 'u': '8', 'v': '8', 'w': '9', 'x': '9', 'y': '9', 'z': '9',
}

out = ''
for n in phone:
x = n.lower()
o = ord(x)
if o > 96 and o < 123:
out += chars[x.lower()]
else:
out += x
return out

I know your patch was just adding back Q and Z, but there's no need
for a regular expression here.

> --
> You received this message because you are subscribed to the Google Groups "Django developers" group.
> To post to this group, send email to django-d...@googlegroups.com.
> To unsubscribe from this group, send email to django-develop...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.
>
>
>
>

--
http://www.apgwoz.com

Mike Axiak

unread,
Jan 15, 2010, 9:15:13 AM1/15/10
to django-d...@googlegroups.com
If you really want to be fast, you can do the following, a la urllib.quote::

_phone_chars = {'a': '2', 'b': '2', 'c': '2', 'd': '3', 'e': '3',


'f': '3', 'g': '4', 'h': '4', 'i': '4', 'j': '5', 'k':
'5', 'l': '5',
'm': '6', 'n': '6', 'o': '6', 'p': '7', 'q': '7', 'r':
'7', 's': '7',
't': '8', 'u': '8', 'v': '8', 'w': '9', 'x': '9', 'y':
'9', 'z': '9',
}

_phone_chars_compiled = None

def phone2number(szinput):
global _phone_chars_compiled
if _phone_chars_compiled is None:
_phone_chars_compiled = {}
for i in range(256):
a = chr(i)
_phone_chars_compiled[a] = _phone_chars.get(a, a)

return ''.join(map(_phone_chars_compiled.__getitem__, szinput))

It yields a 4x speedup over your code (which constructs the map on
every iteration?). Of course we're talking about microseconds, but I
guess every bit counts...

Cheers,
Mike

Mike Axiak

unread,
Jan 15, 2010, 9:27:16 AM1/15/10
to django-d...@googlegroups.com
I forgot to handle upper case. Note that the following is (~5%)
faster than calling .upper() on the input::

def phone2numeric(value):


global _phone_chars_compiled
if _phone_chars_compiled is None:
_phone_chars_compiled = {}
for i in range(256):
a = chr(i)

b = a.upper()


_phone_chars_compiled[a] = _phone_chars.get(a, a)

_phone_chars_compiled[b] = _phone_chars.get(b, b)

return ''.join(map(_phone_chars_compiled.__getitem__, value))

Of course, premature optimization is the root of all evil. There is
certainly a lot more code here, and I'm not sure phone2numeric is that
often called in applications. (As opposed to database/template
operations.) I'll leave you with a great comment on python
optimization [1].

Cheers,
Mike

1: http://www.codeirony.com/?p=9

Łukasz Rekucki

unread,
Jan 15, 2010, 9:30:41 AM1/15/10
to django-d...@googlegroups.com
Or you could use the builtin maketrans/translate pair:

import string
_phone2number_transtab = string.maketrans(string.ascii_letters,
"22233344455566677778889999"*2)

def phone2number(szinput):
return szinput.translate(_phone2number_transtab)

2010/1/15 Mike Axiak <mi...@axiak.net>:

--
Łukasz Rekucki

Andrew Gwozdziewycz

unread,
Jan 15, 2010, 9:42:22 AM1/15/10
to django-d...@googlegroups.com
Right. Not constructing the map every time is an obvious enhancement.
My point was simply that using a regex for this particular simple task
doesn't make much sense.

On Fri, Jan 15, 2010 at 9:15 AM, Mike Axiak <mi...@axiak.net> wrote:

Reply all
Reply to author
Forward
0 new messages