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_en import *
from mycroft.util.lang.format_pt import * from mycroft.util.lang.format_pt import *
from mycroft.util.lang.format_it 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): 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: Returns:
(str): The formatted string. (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 # Convert to spoken representation in appropriate language
lang_lower = str(lang).lower() lang_lower = str(lang).lower()
if lang_lower.startswith("en"): if lang_lower.startswith("en"):
return nice_number_en(result) return nice_number_en(number, speech, denominators)
elif lang_lower.startswith("pt"): elif lang_lower.startswith("pt"):
return nice_number_pt(result) return nice_number_pt(number, speech, denominators)
elif lang_lower.startswith("it"): elif lang_lower.startswith("it"):
return nice_number_it(result) return nice_number_it(number, speech, denominators)
elif lang_lower.startswith("fr"): 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, # Default to the raw number for unsupported languages,
# hopefully the STT engine will pronounce understandably. # 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 # Default to just returning the numeric value
return str(number) 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. # limitations under the License.
# #
from mycroft.util.lang.format_common import convert_to_mixed_fraction
NUM_STRING_EN = { NUM_STRING_EN = {
0: 'zero', 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 Helper for for nice_number
Convert (1 1 3) to spoken value like "1 and 1 third" Convert (1 1 3) to spoken value like "1 and 1 third"
Args: Args:
mixed (int,int,int): the mixed number; whole, numerator, denominator number (int or float): the float to format
Return: speech (bool): format for speech (True) or display (False)
(str): spoken version of the number 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: if num == 0:
return str(whole) return str(whole)
den_str = FRACTION_STRING_EN[den] den_str = FRACTION_STRING_EN[den]

View File

@ -16,10 +16,10 @@
# #
""" Format functions for french (fr) """ 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 = { NUM_STRING_FR = {
0: 'zéro', 0: 'zéro',
1: 'un', 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 Helper for nice_number
Convert (1 1/3) to spoken value like "1 et 1 tiers" Convert (1 1/3) to spoken value like "1 et 1 tiers"
Args: Args:
mixed (int,int,int): the mixed number; whole, numerator, denominator number (int or float): the float to format
Return: speech (bool): format for speech (True) or display (False)
(str): spoken version of the number denominators (iter of ints): denominators to use, default [1 .. 20]
Returns:
(str): The formatted string.
""" """
strNumber = ""
whole = 0
num = 0
den = 0
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 whole, num, den = result
if not speech:
if num == 0: if num == 0:
# if the number is an integer, nothing to do strNumber = '{:,}'.format(whole)
return str(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] den_str = FRACTION_STRING_FR[den]
# if it is not an integer # if it is not an integer
if whole == 0: if whole == 0:
# if there is no whole number # if there is no whole number
if num == 1: if num == 1:
# if numerator is 1, return "un demi", for example # if numerator is 1, return "un demi", for example
return_string = 'un {}'.format(den_str) strNumber = 'un {}'.format(den_str)
else: else:
# else return "quatre tiers", for example # else return "quatre tiers", for example
return_string = '{} {}'.format(num, den_str) strNumber = '{} {}'.format(num, den_str)
elif num == 1: elif num == 1:
# if there is a whole number and numerator is 1 # if there is a whole number and numerator is 1
if den == 2: if den == 2:
# if denominator is 2, return "1 et demi", for example # if denominator is 2, return "1 et demi", for example
return_string = '{} et {}'.format(whole, den_str) strNumber = '{} et {}'.format(whole, den_str)
else: else:
# else return "1 et 1 tiers", for example # else return "1 et 1 tiers", for example
return_string = '{} et 1 {}'.format(whole, den_str) strNumber = '{} et 1 {}'.format(whole, den_str)
else: else:
# else return "2 et 3 quart", for example # else return "2 et 3 quart", for example
return_string = '{} et {} {}'.format(whole, num, den_str) strNumber = '{} et {} {}'.format(whole, num, den_str)
if num > 1 and den != 3: if num > 1 and den != 3:
# if the numerator is greater than 1 and the denominator # if the numerator is greater than 1 and the denominator
# is not 3 ("tiers"), add an s for plural # is not 3 ("tiers"), add an s for plural
return_string += 's' strNumber += 's'
return return_string return strNumber
def pronounce_number_fr(num, places=2): def pronounce_number_fr(num, places=2):

View File

@ -15,6 +15,7 @@
# limitations under the License. # limitations under the License.
# #
from mycroft.util.lang.format_common import convert_to_mixed_fraction
NUM_STRING_IT = { NUM_STRING_IT = {
0: 'zero', 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 Helper for for nice_number adapted to italian
adapted to italian fron en version 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" Convert (1 1 3) to spoken value like "1 e un terzo"
Args: Args:
mixed (int,int,int): the mixed number; whole, numerator, denominator number (int or float): the float to format
Return: speech (bool): format for speech (True) or display (False)
(str): spoken version of the number 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: if num == 0:
return str(whole) return str(whole)
# denominatore # denominatore

View File

@ -15,6 +15,7 @@
# limitations under the License. # limitations under the License.
# #
from mycroft.util.lang.format_common import convert_to_mixed_fraction
FRACTION_STRING_PT = { FRACTION_STRING_PT = {
2: 'meio', 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 """ """ 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 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: if num == 0:
return str(whole) return str(whole)
# denominador # denominador

View File

@ -23,10 +23,13 @@ from mycroft.util.format import pronounce_number
NUMBERS_FIXTURE_FR = { NUMBERS_FIXTURE_FR = {
1.435634: '1.436', 1.435634: '1,436',
2: '2', 2: '2',
5.0: '5', 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', 0.5: 'un demi',
1.333: '1 et 1 tiers', 1.333: '1 et 1 tiers',
2.666: '2 et 2 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", self.assertEqual(nice_number(5.5, lang="fr-fr",
denominators=[1, 2, 3]), denominators=[1, 2, 3]),
'5 et demi', '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", nice_number(5.5, lang="fr-fr",
denominators=[1, 2, 3]))) denominators=[1, 2, 3])))
self.assertEqual(nice_number(2.333, denominators=[1, 2]), self.assertEqual(nice_number(2.333, lang="fr-fr",
'2.333', denominators=[1, 2]),
'should format 2.333 as 2.333 not {}'.format( '2,333',
'should format 2.333 as 2,333 not {}'.format(
nice_number(2.333, lang="fr-fr", nice_number(2.333, lang="fr-fr",
denominators=[1, 2]))) denominators=[1, 2])))
def test_no_speech_fr(self): 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', '6 7/9',
'should format 6.777 as 6 7/9 not {}'.format( 'should format 6.777 as 6 7/9 not {}'.format(
nice_number(6.777, lang="fr-fr", speech=False))) 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', '6',
'should format 6.0 as 6 not {}'.format( 'should format 6.0 as 6 not {}'.format(
nice_number(6.0, lang="fr-fr", speech=False))) 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): # def pronounce_number(number, lang="en-us", places=2):