Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/pronounce digits #41

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions lingua_franca/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
_REGISTERED_FUNCTIONS = ("nice_number",
"nice_time",
"pronounce_number",
"pronounce_digits",
"pronounce_lang",
"nice_response",
"nice_duration")
Expand Down Expand Up @@ -317,6 +318,23 @@ def pronounce_number(number, lang='', places=2, short_scale=True,
"""


@localized_function()
def pronounce_digits(number, places=2, all_digits=False, lang=""):
"""
Pronounce a number's digits, either colloquially or in full
In English, the colloquial way is usually to read two digits at a time,
treating each pair as a single number.
Examples:
>>> pronounce_number(127, all_digits=False)
'one twenty seven'
>>> pronounce_number(127, all_digits=True)
'one two seven'
Args:
number (int|float)
all_digits (bool): read every digit, rather than two digits at a time
"""


def nice_date(dt, lang='', now=None):
"""
Format a datetime to a pronounceable date
Expand Down
36 changes: 36 additions & 0 deletions lingua_franca/lang/format_en.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,42 @@ def nice_number_en(number, speech=True, denominators=range(1, 21)):
return return_string


def pronounce_digits_en(number, places=2, all_digits=False):
if isinstance(number, str):
op_val = number
decimal_part = ""
if "." in number:
op_val, decimal_part = number.split(".")
if all_digits:
op_val = " ".join([pronounce_number_en(int(ch))
for ch in op_val])
else:

op_val = pronounce_number_en(int(op_val))
if decimal_part:
decimal_part = " ".join([pronounce_number_en(int(ch))
for ch in decimal_part[:places]])
return op_val + " point " + decimal_part
return op_val

if "." in str(number):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we treat non-str numbers differently here instead of just starting with number = str(number)?

op_val, decimal_part = str(number).split(".")
decimal_part = " ".join([pronounce_number_en(int(ch))
for ch in decimal_part[:places]])
else:
op_val = str(number)
decimal_part = ""

if all_digits:
op_val = " ".join([pronounce_number_en(int(ch))
for ch in op_val])
else:
op_val = pronounce_number_en(int(op_val))
if decimal_part:
return op_val + " point " + decimal_part
return op_val

Comment on lines +68 to +102
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of pronounce_digits_en function

The function pronounce_digits_en has been added to handle the pronunciation of digits. It allows for specifying the number of decimal places and whether to pronounce all digits. Here are a few observations and suggestions:

  1. Type Checking: The use of isinstance to differentiate behavior based on the type of number is a good practice. This ensures that the function can handle different types of inputs robustly.
  2. Handling of Decimal Parts: The logic to split and handle the decimal part of the number is clear and well-implemented. However, consider adding error handling for cases where the input is not as expected (e.g., non-numeric strings).
  3. Use of List Comprehensions: The use of list comprehensions for constructing the spoken form of the number is efficient and Pythonic.
  4. Potential Refactoring: The blocks of code handling the string and non-string inputs are quite similar and could potentially be refactored to reduce duplication. This could improve maintainability.
  5. Documentation: Ensure that the function's docstring is updated to reflect all parameters and possible edge cases.

Overall, the function implementation looks solid but consider the above points to further enhance the code quality and robustness.


def pronounce_number_en(number, places=2, short_scale=True, scientific=False,
ordinals=False):
"""
Expand Down
52 changes: 50 additions & 2 deletions test/unittests/test_format_en.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,11 @@
from lingua_franca.format import pronounce_number
from lingua_franca.format import date_time_format
from lingua_franca.format import join_list
from lingua_franca.format import pronounce_lang
from lingua_franca.format import pronounce_lang, pronounce_digits
from lingua_franca.time import default_timezone, set_default_tz, now_local, \
to_local



def setUpModule():
load_language('en')
set_default_lang('en')
Expand Down Expand Up @@ -117,6 +116,55 @@ def test_no_speech(self):


class TestPronounceNumber(unittest.TestCase):
def test_pronounce_digits_float(self):
self.assertEqual(pronounce_digits(0.5), "zero point five")
self.assertEqual(pronounce_digits(1.1235), "one point one two")
self.assertEqual(pronounce_digits(10.999999), "ten point nine nine")
self.assertEqual(pronounce_digits(10.999999, places=0), "ten")
Comment on lines +122 to +123
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These aren't correctly rounded; I think this should either treat places as "significant figures", or specifically note in the docstring that places are not sig figs and decimals are truncated as opposed to rounded

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe an extra flag for rounding or not defaulting to True?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect its parsing as a string instead of using round()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is a formating method should not modify input, imho not rounding is the expected behaviour and rounding should be done before calling method if needed or via optional flag. This is reading digits not pronouncing the number, the rounding feels application specific even if I can't come up with a good use case where we don't want it....

self.assertEqual(pronounce_digits(15.0), "fifteen point zero")
self.assertEqual(pronounce_digits(20.0001, places=99),
"twenty point zero zero zero one")
self.assertEqual(pronounce_digits(27.23467875, places=4),
"twenty seven point two three four six")
self.assertEqual(pronounce_digits(0.5, all_digits=True),
"zero point five")
self.assertEqual(pronounce_digits(012345.5, all_digits=True),
"one two three four five point five")
self.assertEqual(pronounce_digits(27.23467875, all_digits=True),
"two seven point two three")
self.assertEqual(pronounce_digits(199.9990, places=5),
"one hundred and ninety nine point nine nine nine")
self.assertEqual(pronounce_digits(199.9990, places=5, all_digits=True),
"one nine nine point nine nine nine")

def test_pronounce_digits_int(self):
self.assertEqual(pronounce_digits(0), "zero")
self.assertEqual(pronounce_digits(1), "one")
self.assertEqual(pronounce_digits(199999, all_digits=True),
"one nine nine nine nine nine")
self.assertEqual(pronounce_digits(10999999, all_digits=True),
"one zero nine nine nine nine nine nine")
self.assertEqual(pronounce_digits(150, all_digits=True),
"one five zero")
self.assertEqual(pronounce_digits(200001, all_digits=True),
"two zero zero zero zero one")

def test_pronounce_digits_str(self):
self.assertEqual(pronounce_digits("0"), "zero")
self.assertEqual(pronounce_digits("01"), "one")
self.assertEqual(pronounce_digits("01", all_digits=True),
"zero one")
self.assertEqual(pronounce_digits("199999", all_digits=True),
"one nine nine nine nine nine")
self.assertEqual(pronounce_digits("199.999", all_digits=True),
"one nine nine point nine nine")
self.assertEqual(pronounce_digits("199.999", places=5,
all_digits=True),
"one nine nine point nine nine nine")
self.assertEqual(pronounce_digits("199.9990", places=5,
all_digits=True),
"one nine nine point nine nine nine zero")

def test_convert_int(self):
self.assertEqual(pronounce_number(0), "zero")
self.assertEqual(pronounce_number(1), "one")
Expand Down