Issue-1411 - Leave all nice_number formatting to language

pull/1416/head
Cakeh 2018-02-12 12:02:04 +01:00
parent 9b5f4dd09c
commit c45d228d91
7 changed files with 200 additions and 105 deletions

View File

@ -16,7 +16,10 @@
from mycroft.util.lang.format_en import *
from mycroft.util.lang.format_pt import *
from mycroft.util.lang.format_it import *
from mycroft.util.lang.format_fr import *
from mycroft.util.lang.format_fr import nice_number_fr
from mycroft.util.lang.format_fr import nice_time_fr
from mycroft.util.lang.format_fr import pronounce_number_fr
def nice_number(number, lang="en-us", speech=True, denominators=None):
@ -32,29 +35,16 @@ def nice_number(number, lang="en-us", speech=True, denominators=None):
Returns:
(str): The formatted string.
"""
result = _convert_to_mixed_fraction(number, denominators)
if not result:
# Give up, just represent as a 3 decimal number
return str(round(number, 3))
if not speech:
whole, num, den = result
if num == 0:
# TODO: Number grouping? E.g. "1,000,000"
return str(whole)
else:
return '{} {}/{}'.format(whole, num, den)
# Convert to spoken representation in appropriate language
lang_lower = str(lang).lower()
if lang_lower.startswith("en"):
return nice_number_en(result)
return nice_number_en(number, speech, denominators)
elif lang_lower.startswith("pt"):
return nice_number_pt(result)
return nice_number_pt(number, speech, denominators)
elif lang_lower.startswith("it"):
return nice_number_it(result)
return nice_number_it(number, speech, denominators)
elif lang_lower.startswith("fr"):
return nice_number_fr(result)
return nice_number_fr(number, speech, denominators)
# Default to the raw number for unsupported languages,
# hopefully the STT engine will pronounce understandably.
@ -111,35 +101,3 @@ def pronounce_number(number, lang="en-us", places=2):
# Default to just returning the numeric value
return str(number)
def _convert_to_mixed_fraction(number, denominators):
"""
Convert floats to components of a mixed fraction representation
Returns the closest fractional representation using the
provided denominators. For example, 4.500002 would become
the whole number 4, the numerator 1 and the denominator 2
Args:
number (float): number for convert
denominators (iter of ints): denominators to use, default [1 .. 20]
Returns:
whole, numerator, denominator (int): Integers of the mixed fraction
"""
int_number = int(number)
if int_number == number:
return int_number, 0, 1 # whole number, no fraction
frac_number = abs(number - int_number)
if not denominators:
denominators = range(1, 21)
for denominator in denominators:
numerator = abs(frac_number) * denominator
if (abs(numerator - round(numerator)) < 0.01): # 0.01 accuracy
break
else:
return None
return int_number, int(round(numerator)), denominator

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
#
# Copyright 2017 Mycroft AI Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
def convert_to_mixed_fraction(number, denominators):
"""
Convert floats to components of a mixed fraction representation
Returns the closest fractional representation using the
provided denominators. For example, 4.500002 would become
the whole number 4, the numerator 1 and the denominator 2
Args:
number (float): number for convert
denominators (iter of ints): denominators to use, default [1 .. 20]
Returns:
whole, numerator, denominator (int): Integers of the mixed fraction
"""
int_number = int(number)
if int_number == number:
return int_number, 0, 1 # whole number, no fraction
frac_number = abs(number - int_number)
if not denominators:
denominators = range(1, 21)
for denominator in denominators:
numerator = abs(frac_number) * denominator
if (abs(numerator - round(numerator)) < 0.01): # 0.01 accuracy
break
else:
return None
return int_number, int(round(numerator)), denominator

View File

@ -15,6 +15,7 @@
# limitations under the License.
#
from mycroft.util.lang.format_common import convert_to_mixed_fraction
NUM_STRING_EN = {
0: 'zero',
@ -71,18 +72,34 @@ FRACTION_STRING_EN = {
}
def nice_number_en(mixed):
def nice_number_en(number, speech, denominators):
"""
Helper for for nice_number
Convert (1 1 3) to spoken value like "1 and 1 third"
Args:
mixed (int,int,int): the mixed number; whole, numerator, denominator
Return:
(str): spoken version of the number
number (int or float): the float to format
speech (bool): format for speech (True) or display (False)
denominators (iter of ints): denominators to use, default [1 .. 20]
Returns:
(str): The formatted string.
"""
whole, num, den = mixed
result = convert_to_mixed_fraction(number, denominators)
if not result:
# Give up, just represent as a 3 decimal number
return str(round(number, 3))
whole, num, den = result
if not speech:
if num == 0:
# TODO: Number grouping? E.g. "1,000,000"
return str(whole)
else:
return '{} {}/{}'.format(whole, num, den)
if num == 0:
return str(whole)
den_str = FRACTION_STRING_EN[den]

View File

@ -16,10 +16,10 @@
#
""" Format functions for french (fr)
Todo:
* nice_number should leave number formatting to nice_number_fr
"""
from mycroft.util.lang.format_common import convert_to_mixed_fraction
NUM_STRING_FR = {
0: 'zéro',
1: 'un',
@ -71,48 +71,73 @@ FRACTION_STRING_FR = {
}
def nice_number_fr(result):
def nice_number_fr(number, speech, denominators):
"""
Helper for nice_number
Convert (1 1/3) to spoken value like "1 et 1 tiers"
Args:
mixed (int,int,int): the mixed number; whole, numerator, denominator
Return:
(str): spoken version of the number
number (int or float): the float to format
speech (bool): format for speech (True) or display (False)
denominators (iter of ints): denominators to use, default [1 .. 20]
Returns:
(str): The formatted string.
"""
whole, num, den = result
if num == 0:
# if the number is an integer, nothing to do
return str(whole)
den_str = FRACTION_STRING_FR[den]
# if it is not an integer
if whole == 0:
# if there is no whole number
if num == 1:
# if numerator is 1, return "un demi", for example
return_string = 'un {}'.format(den_str)
else:
# else return "quatre tiers", for example
return_string = '{} {}'.format(num, den_str)
elif num == 1:
# if there is a whole number and numerator is 1
if den == 2:
# if denominator is 2, return "1 et demi", for example
return_string = '{} et {}'.format(whole, den_str)
else:
# else return "1 et 1 tiers", for example
return_string = '{} et 1 {}'.format(whole, den_str)
else:
# else return "2 et 3 quart", for example
return_string = '{} et {} {}'.format(whole, num, den_str)
if num > 1 and den != 3:
# if the numerator is greater than 1 and the denominator
# is not 3 ("tiers"), add an s for plural
return_string += 's'
strNumber = ""
whole = 0
num = 0
den = 0
return return_string
result = convert_to_mixed_fraction(number, denominators)
if not result:
# Give up, just represent as a 3 decimal number
whole = round(number, 3)
else:
whole, num, den = result
if not speech:
if num == 0:
strNumber = '{:,}'.format(whole)
strNumber = strNumber.replace(",", " ")
strNumber = strNumber.replace(".", ",")
return strNumber
else:
return '{} {}/{}'.format(whole, num, den)
else:
if num == 0:
# if the number is not a fraction, nothing to do
strNumber = str(whole)
strNumber = strNumber.replace(".", ",")
return strNumber
den_str = FRACTION_STRING_FR[den]
# if it is not an integer
if whole == 0:
# if there is no whole number
if num == 1:
# if numerator is 1, return "un demi", for example
strNumber = 'un {}'.format(den_str)
else:
# else return "quatre tiers", for example
strNumber = '{} {}'.format(num, den_str)
elif num == 1:
# if there is a whole number and numerator is 1
if den == 2:
# if denominator is 2, return "1 et demi", for example
strNumber = '{} et {}'.format(whole, den_str)
else:
# else return "1 et 1 tiers", for example
strNumber = '{} et 1 {}'.format(whole, den_str)
else:
# else return "2 et 3 quart", for example
strNumber = '{} et {} {}'.format(whole, num, den_str)
if num > 1 and den != 3:
# if the numerator is greater than 1 and the denominator
# is not 3 ("tiers"), add an s for plural
strNumber += 's'
return strNumber
def pronounce_number_fr(num, places=2):

View File

@ -15,6 +15,7 @@
# limitations under the License.
#
from mycroft.util.lang.format_common import convert_to_mixed_fraction
NUM_STRING_IT = {
0: 'zero',
@ -70,7 +71,7 @@ FRACTION_STRING_IT = {
}
def nice_number_it(mixed):
def nice_number_it(number, speech, denominators):
"""
Helper for for nice_number adapted to italian
adapted to italian fron en version
@ -78,12 +79,27 @@ def nice_number_it(mixed):
Convert (1 1 3) to spoken value like "1 e un terzo"
Args:
mixed (int,int,int): the mixed number; whole, numerator, denominator
Return:
(str): spoken version of the number
number (int or float): the float to format
speech (bool): format for speech (True) or display (False)
denominators (iter of ints): denominators to use, default [1 .. 20]
Returns:
(str): The formatted string.
"""
whole, num, den = mixed
result = convert_to_mixed_fraction(number, denominators)
if not result:
# Give up, just represent as a 3 decimal number
return str(round(number, 3))
whole, num, den = result
if not speech:
if num == 0:
# TODO: Number grouping? E.g. "1,000,000"
return str(whole)
else:
return '{} {}/{}'.format(whole, num, den)
if num == 0:
return str(whole)
# denominatore

View File

@ -15,6 +15,7 @@
# limitations under the License.
#
from mycroft.util.lang.format_common import convert_to_mixed_fraction
FRACTION_STRING_PT = {
2: 'meio',
@ -42,9 +43,23 @@ FRACTION_STRING_PT = {
}
def nice_number_pt(result):
def nice_number_pt(number, speech, denominators):
""" Portuguese conversion for nice_number """
result = convert_to_mixed_fraction(number, denominators)
if not result:
# Give up, just represent as a 3 decimal number
return str(round(number, 3))
whole, num, den = result
if not speech:
if num == 0:
# TODO: Number grouping? E.g. "1,000,000"
return str(whole)
else:
return '{} {}/{}'.format(whole, num, den)
if num == 0:
return str(whole)
# denominador

View File

@ -23,10 +23,13 @@ from mycroft.util.format import pronounce_number
NUMBERS_FIXTURE_FR = {
1.435634: '1.436',
1.435634: '1,436',
2: '2',
5.0: '5',
0.027: '0.027',
0.027: '0,027',
1234567890: '1234567890',
12345.67890: '12345,679',
0.027: '0,027',
0.5: 'un demi',
1.333: '1 et 1 tiers',
2.666: '2 et 2 tiers',
@ -65,24 +68,37 @@ class TestNiceNumberFormat_fr(unittest.TestCase):
self.assertEqual(nice_number(5.5, lang="fr-fr",
denominators=[1, 2, 3]),
'5 et demi',
'should format 5.5 as 5 and a half not {}'.format(
'should format 5.5 as 5 et demi not {}'.format(
nice_number(5.5, lang="fr-fr",
denominators=[1, 2, 3])))
self.assertEqual(nice_number(2.333, denominators=[1, 2]),
'2.333',
'should format 2.333 as 2.333 not {}'.format(
self.assertEqual(nice_number(2.333, lang="fr-fr",
denominators=[1, 2]),
'2,333',
'should format 2.333 as 2,333 not {}'.format(
nice_number(2.333, lang="fr-fr",
denominators=[1, 2])))
def test_no_speech_fr(self):
self.assertEqual(nice_number(6.777, speech=False),
self.assertEqual(nice_number(6.777, lang="fr-fr", speech=False),
'6 7/9',
'should format 6.777 as 6 7/9 not {}'.format(
nice_number(6.777, lang="fr-fr", speech=False)))
self.assertEqual(nice_number(6.0, speech=False),
self.assertEqual(nice_number(6.0, lang="fr-fr", speech=False),
'6',
'should format 6.0 as 6 not {}'.format(
nice_number(6.0, lang="fr-fr", speech=False)))
self.assertEqual(nice_number(1234567890, lang="fr-fr", speech=False),
'1 234 567 890',
'should format 1234567890 as'
'1 234 567 890 not {}'.format(
nice_number(1234567890, lang="fr-fr",
speech=False)))
self.assertEqual(nice_number(12345.6789, lang="fr-fr", speech=False),
'12 345,679',
'should format 12345.6789 as'
'12 345,679 not {}'.format(
nice_number(12345.6789, lang="fr-fr",
speech=False)))
# def pronounce_number(number, lang="en-us", places=2):