From ad19bb601023830c2ec8f07b939a3ea4b84f1861 Mon Sep 17 00:00:00 2001 From: makhosravi Date: Sat, 19 Feb 2022 16:24:25 +0330 Subject: [PATCH 1/2] update(NumberToWords): improve the algorithm, add new test cases, remove string extension --- README.md | 18 +- example/example_number_to_words.dart | 17 +- .../constants/number_to_words/constants.dart | 13 +- .../core/number_to_words/number_to_words.dart | 124 ++++++------ test/test_number_to_words.dart | 179 +++++------------- 5 files changed, 128 insertions(+), 223 deletions(-) diff --git a/README.md b/README.md index 8b03499..41b635c 100644 --- a/README.md +++ b/README.md @@ -89,28 +89,16 @@ words.convertWordsToNumberString(); // '3212' as String - #### Converting Persian numbers to word - [source](https://github.com/persian-tools/dart-persian-tools/blob/master/lib/src/core/number_to_words/number_to_words.dart) ```dart -final stringDigit = '257,433'; final intDigit = -128; -/// use [numberToWordsString] method to convert [stringDigit] to persian - -numberToWordsString(stringDigit); // 'دویست و پنجاه و هفت هزار و چهارصد و سی و سه' - -/// [numberToWordsString] also has an optional parameter -/// by default [ordinal] is [false], [true] makes the output an ordinal word - -numberToWordsString(stringDigit, ordinal: true); // 'دویست و پنجاه و هفت هزار و چهارصد و سی و سوم' - -/// use [numberToWordsInt] method to convert [intDigit] to persian +/// use [numberToWords] method to convert [intDigit] to persian numberToWords(intDigit); // 'منفی صد و بیست و هشت' -/// [numberToWordsInt] also has an optional parameter +/// [numberToWords] also has an optional parameter /// by default [ordinal] is [false], [true] makes the output an ordinal word numberToWords(intDigit, ordinal: true); // 'منفی صد و بیست و هشتم' -/// you can simply use extension methods on int or String objects -stringDigit.convertNumToWords(); // 'دویست و پنجاه و هفت هزار و چهارصد و سی و سه' - +/// you can simply use extension methods on int objects intDigit.convertNumToWords(); // 'منفی صد و بیست و هشت' ``` diff --git a/example/example_number_to_words.dart b/example/example_number_to_words.dart index 65fed0b..39ef410 100644 --- a/example/example_number_to_words.dart +++ b/example/example_number_to_words.dart @@ -1,28 +1,15 @@ import 'package:persian_tools/persian_tools.dart'; void main() { - final stringDigit = '257,433'; final intDigit = -128; - /// use [numberToWordsString] method to convert [stringDigit] to persian - print(numberToWordsString( - stringDigit)); // 'دویست و پنجاه و هفت هزار و چهارصد و سی و سه' - - /// [numberToWordsString] also has an optional parameter - /// by default [ordinal] is [false], [true] makes the output an ordinal word - print(numberToWordsString(stringDigit, - ordinal: true)); // 'دویست و پنجاه و هفت هزار و چهارصد و سی و سوم' - - /// use [numberToWordsInt] method to convert [intDigit] to persian + /// use [numberToWords] method to convert [intDigit] to persian print(numberToWords(intDigit)); // 'منفی صد و بیست و هشت' - /// [numberToWordsInt] also has an optional parameter + /// [numberToWords] also has an optional parameter /// by default [ordinal] is [false], [true] makes the output an ordinal word print(numberToWords(intDigit, ordinal: true)); // 'منفی صد و بیست و هشتم' /// you can simply use extension methods on int or String objects - print(stringDigit - .convertNumToWords()); // 'دویست و پنجاه و هفت هزار و چهارصد و سی و سه' - print(intDigit.convertNumToWords()); // 'منفی صد و بیست و هشت' } diff --git a/lib/src/constants/number_to_words/constants.dart b/lib/src/constants/number_to_words/constants.dart index 0324365..43bee10 100644 --- a/lib/src/constants/number_to_words/constants.dart +++ b/lib/src/constants/number_to_words/constants.dart @@ -1,13 +1,11 @@ -const base = 1000; +const errorMessage = + 'PersianTools: numberToWords - the number must be a safe integer less than 16 digits'; const zeroFa = 'صفر'; -const endsWithAnd = ' و '; - -const scale = ['', 'هزار', 'میلیون', 'میلیارد']; +const andFa = ' و '; const numberToWord = { - 0: '', 1: 'یک', 2: 'دو', 3: 'سه', @@ -44,4 +42,9 @@ const numberToWord = { 700: 'هفتصد', 800: 'هشتصد', 900: 'نهصد', + 1000: 'هزار', + 1000000: 'میلیون', + 1000000000: 'میلیارد', + 1000000000000: 'تریلیون', + 1000000000000000: 'کوآدریلیون', }; diff --git a/lib/src/core/number_to_words/number_to_words.dart b/lib/src/core/number_to_words/number_to_words.dart index 722b16e..a98491d 100644 --- a/lib/src/core/number_to_words/number_to_words.dart +++ b/lib/src/core/number_to_words/number_to_words.dart @@ -1,82 +1,88 @@ import 'package:persian_tools/persian_tools.dart'; import 'package:persian_tools/src/constants/number_to_words/constants.dart'; -import 'package:persian_tools/src/internal_methods.dart'; -/// Gets an [int] with 3 or less digits as input and converts it to persian -String _numToWord(int number) { - var unit = 100; - var result = ''; - - while (unit > 0) { - if ((number / unit).floor() * unit != 0) { - if (numberToWord.containsKey(number)) { - result += numberToWord[number]!; - break; - } else { - result += '${numberToWord[(number / unit).floor() * unit]} و '; - number %= unit; - } - } - unit = (unit / 10).floor(); - } - return result; +/// Gets an [int] as input and checks if it's a negative number +bool _isNegative(int number) { + return number < 0; } -/// Checks current [input] for negative [value] and deploying converting process -String _convert(int number, bool ordinal) { - var result = []; - - final isNegative = number < 0; - number = isNegative ? number * -1 : number; - - while (number > 0) { - result.add(_numToWord(number % base)); - number = (number / base).floor(); - } - if (result.length > 4) return ''; - - for (var i = 0; i < result.length; i++) { - if (result[i] != '' && i != 0) result[i] += ' ${scale[i]} و '; - } - result = result.reversed.toList(); - - var words = result.join(''); - - if (words.endsWith(endsWithAnd)) { - words = words.substring(0, (words.length - 3)); - } +/// Returns error message for [input] with more than 16 digits +String _numberIsNotValidError() { + return errorMessage; +} - words = trim(isNegative ? 'منفی $words' : words); +/// Gets an [int] as input and if exists, returns persian word +String _getWord(int number) { + return numberToWord[number] ?? ''; +} - if (ordinal) words = addOrdinalSuffix(words); +/// Gets an [String] input and add negative persian prefix to it +String _addNegativeSuffix(String number) { + return 'منفی $number'; +} - return words; +/// Gets an [int] as input and returns [unitName] for numbers over 1000 +/// [_getUnitName] parameter is calculated by subtracting current [map] key +/// from [separated] length multiplied by 3 +String _getUnitName(int numberOfZeros) { + return numberOfZeros == 0 + ? '' + : numberToWord[int.parse('1${"0" * numberOfZeros}')]!; } -/// Returns Persian word of the given number in String. Number can contain commas. -/// You can determine returned string has ordinal suffix or not by `ordinal` flag. -String? numberToWordsString(String number, {bool ordinal = false}) { - if (number.isEmpty) return null; - if (number == '0') return zeroFa; +/// Gets an [int] with 3 or less digits as input and converts it to persian +/// [num] between 1 & 19,except for 10, are converted directly like other +/// numbers that divisible by 10 +/// For other numbers, the remainder of dividing by 10 would be passed to +/// [_transformToWord] +String _transformToWord(int number) { + if (number == 0) return ''; + if (number <= 19 && number != 10) return _getWord(number); + + final residual = number <= 99 ? number % 10 : number % 100; + return residual == 0 + ? _getWord(number) + : '${_getWord(number - residual)} و ${_transformToWord(residual)}'; +} - return _convert(removeCommas(number).toInt(), ordinal); +/// Transform [input] to string, breaks the number into three digit +/// parts, assign the string to [separated], get each part's [unitName] +/// and [transformedVal] inside [map] function of [numberArr]. each separated +/// part is handled in [map]. After [map] function, [where] function checks +/// the result created by [map] and ignores the empty items. +/// Finally [toList] combines and returns the result of the parts as a [String] +String _performer(int number) { + if (number <= 999) return _transformToWord(number); + final separated = addCommas(number.toString()).split(','); + final numberArr = separated + .asMap() + .entries + .map((e) { + final transformedVal = _transformToWord(int.parse(e.value, radix: 10)); + final unitName = _getUnitName((separated.length - (e.key + 1)) * 3); + return transformedVal.isNotEmpty ? '$transformedVal $unitName' : ''; + }) + .where((element) => element.length > 1) + .toList(); + return numberArr.join(andFa).trim(); } /// Returns Persian word of the given number in int You can determine /// returned string has ordinal suffix or not by `ordinal` flag. +/// The current [input] is passed to [_performer] as positive integer +/// If [input] is negative, prefix would be added to [_performer] result +/// If `ordinal` is true, suffix would be added to the result of [tmpResult] String numberToWords(int number, {bool ordinal = false}) { if (number == 0) return zeroFa; - return _convert(number, ordinal); -} - -/// [String] Extension wrapper to convert number to Persian word on String object -extension NumberToPersianWordsString on String { - String? convertNumToWords({bool ordinal = false}) => - numberToWordsString(this, ordinal: ordinal); + if (number.toString().length > 16) return _numberIsNotValidError(); + final _tmpResult = _isNegative(number) + ? _addNegativeSuffix(_performer(number.abs())) + : _performer(number); + return ordinal ? addOrdinalSuffix(_tmpResult) : _tmpResult; } /// [int] Extension wrapper to convert number to Persian word on int object extension NumberToPersianWordsInt on int { - String? convertNumToWords({bool ordinal = false}) => + String convertNumToWords({bool ordinal = false}) => numberToWords(this, ordinal: ordinal); } diff --git a/test/test_number_to_words.dart b/test/test_number_to_words.dart index 1e85d82..134a63d 100644 --- a/test/test_number_to_words.dart +++ b/test/test_number_to_words.dart @@ -5,94 +5,14 @@ void main() { group('test number_to_words.dart', () { test('test numberToWordsByString function: empty value', () { expect( - numberToWordsString(''), - null, - ); - }); - - //---------------------------------------------------------------------------------------- - - test('test numberToWordsByString function', () { - expect( - numberToWordsString('56'), - equals('پنجاه و شش'), - ); - - expect( - numberToWordsString('256432'), - equals('دویست و پنجاه و شش هزار و چهارصد و سی و دو'), - ); - - expect( - numberToWordsString('2,465,337,400'), + numberToWords(150000000000000000), equals( - 'دو میلیارد و چهارصد و شصت و پنج میلیون و سیصد و سی و هفت هزار و چهارصد'), + 'PersianTools: numberToWords - the number must be a safe integer less than 16 digits'), ); - expect( - numberToWordsString('0'), + numberToWords(000), equals('صفر'), ); - - expect( - numberToWordsString('500,443'), - equals('پانصد هزار و چهارصد و چهل و سه'), - ); - - expect( - numberToWordsString('2,465,337,400', ordinal: true), - equals( - 'دو میلیارد و چهارصد و شصت و پنج میلیون و سیصد و سی و هفت هزار و چهارصدم'), - ); - - expect( - numberToWordsString('500')?.length, - equals(5), - ); - - expect( - numberToWordsString('502375902532527'), - equals(''), - ); - }); - - test('test numberToWordsByString function: with negative values', () { - expect( - numberToWordsString('-87'), - equals('منفی هشتاد و هفت'), - ); - - expect( - numberToWordsString('-256432'), - equals('منفی دویست و پنجاه و شش هزار و چهارصد و سی و دو'), - ); - - expect( - numberToWordsString('-500,443'), - equals('منفی پانصد هزار و چهارصد و چهل و سه'), - ); - - expect( - numberToWordsString('-700,443,000'), - equals('منفی هفتصد میلیون و چهارصد و چهل و سه هزار'), - ); - }); - - test('test numberToWordsByString function: return ordinal words', () { - expect( - numberToWordsString('-700,443,000', ordinal: true), - equals('منفی هفتصد میلیون و چهارصد و چهل و سه هزارم'), - ); - - expect( - numberToWordsString('-700443000', ordinal: true), - equals('منفی هفتصد میلیون و چهارصد و چهل و سه هزارم'), - ); - - expect( - numberToWordsString('700443001', ordinal: true), - equals('هفتصد میلیون و چهارصد و چهل و سه هزار و یکم'), - ); }); //---------------------------------------------------------------------------------------- @@ -103,6 +23,11 @@ void main() { equals('صفر'), ); + expect( + numberToWords(4), + equals('چهار'), + ); + expect( numberToWords(56), equals('پنجاه و شش'), @@ -119,11 +44,6 @@ void main() { 'دو میلیارد و چهارصد و شصت و پنج میلیون و سیصد و سی و هفت هزار و چهارصد'), ); - expect( - numberToWords(0), - equals('صفر'), - ); - expect( numberToWords(500443), equals('پانصد هزار و چهارصد و چهل و سه'), @@ -142,7 +62,30 @@ void main() { expect( numberToWords(502375902532527), - equals(''), + equals( + 'پانصد و دو تریلیون و سیصد و هفتاد و پنج میلیارد و نهصد و دو میلیون و پانصد و سی و دو هزار و پانصد و بیست و هفت'), + ); + + expect( + numberToWords(500), + hasLength(5), + ); + + expect( + numberToWords(30000000000), + equals('سی میلیارد'), + ); + + expect( + numberToWords(987654321), + equals( + 'نهصد و هشتاد و هفت میلیون و ششصد و پنجاه و چهار هزار و سیصد و بیست و یک'), + ); + + expect( + numberToWords(9006199254740992), + equals( + 'نه کوآدریلیون و شش تریلیون و صد و نود و نه میلیارد و دویست و پنجاه و چهار میلیون و هفتصد و چهل هزار و نهصد و نود و دو'), ); }); @@ -174,11 +117,21 @@ void main() { equals('منفی سی اُم'), ); + expect( + numberToWords(-123, ordinal: true), + equals('منفی صد و بیست و سوم'), + ); + expect( numberToWords(33, ordinal: true), equals('سی و سوم'), ); + expect( + numberToWords(45, ordinal: true), + equals('چهل و پنجم'), + ); + expect( numberToWords(-700443000, ordinal: true), equals('منفی هفتصد میلیون و چهارصد و چهل و سه هزارم'), @@ -194,27 +147,21 @@ void main() { test('test numberToWords extension', () { var int = -30; - var str = '555'; - expect(int.convertNumToWords()!, equals('منفی سی')); - expect(str.convertNumToWords()!, equals('پانصد و پنجاه و پنج')); + expect(int.convertNumToWords(), equals('منفی سی')); expect( - '-700443000'.convertNumToWords(), + (-700443000).convertNumToWords(), equals('منفی هفتصد میلیون و چهارصد و چهل و سه هزار'), ); expect( - '-700443000'.convertNumToWords(ordinal: true), + (-700443000).convertNumToWords(ordinal: true), equals('منفی هفتصد میلیون و چهارصد و چهل و سه هزارم'), ); expect( - (-700443000).convertNumToWords(), - equals('منفی هفتصد میلیون و چهارصد و چهل و سه هزار'), - ); - expect( - (-700443000).convertNumToWords(ordinal: true), - equals('منفی هفتصد میلیون و چهارصد و چهل و سه هزارم'), + int.convertNumToWords(ordinal: true), + equals('منفی سی اُم'), ); expect( @@ -227,32 +174,14 @@ void main() { ); expect( - '-700,443,000'.convertNumToWords(), - equals('منفی هفتصد میلیون و چهارصد و چهل و سه هزار'), - ); - expect( - '-700,443,000'.convertNumToWords(ordinal: true), - equals('منفی هفتصد میلیون و چهارصد و چهل و سه هزارم'), - ); - - expect( - '-87'.convertNumToWords(), + (-87).convertNumToWords(), equals('منفی هشتاد و هفت'), ); expect( - '-87'.convertNumToWords(ordinal: true), + (-87).convertNumToWords(ordinal: true), equals('منفی هشتاد و هفتم'), ); - expect( - '87'.convertNumToWords(), - equals('هشتاد و هفت'), - ); - expect( - '87'.convertNumToWords(ordinal: true), - equals('هشتاد و هفتم'), - ); - expect( 87.convertNumToWords(), equals('هشتاد و هفت'), @@ -263,11 +192,7 @@ void main() { ); expect( - 500.convertNumToWords()?.length, - equals(5), - ); - expect( - '500'.convertNumToWords()?.length, + 500.convertNumToWords().length, equals(5), ); @@ -275,10 +200,6 @@ void main() { 0.convertNumToWords(), equals('صفر'), ); - expect( - '0'.convertNumToWords(), - equals('صفر'), - ); }); }); } From fc192c3057c3faee06287448ba78d1c68443345a Mon Sep 17 00:00:00 2001 From: makhosravi Date: Sun, 20 Feb 2022 18:07:05 +0330 Subject: [PATCH 2/2] refactor(NumberToWords): use trim method from internal methods --- lib/src/core/number_to_words/number_to_words.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/core/number_to_words/number_to_words.dart b/lib/src/core/number_to_words/number_to_words.dart index a98491d..e707363 100644 --- a/lib/src/core/number_to_words/number_to_words.dart +++ b/lib/src/core/number_to_words/number_to_words.dart @@ -1,5 +1,6 @@ import 'package:persian_tools/persian_tools.dart'; import 'package:persian_tools/src/constants/number_to_words/constants.dart'; +import 'package:persian_tools/src/internal_methods.dart'; /// Gets an [int] as input and checks if it's a negative number bool _isNegative(int number) { @@ -64,7 +65,7 @@ String _performer(int number) { }) .where((element) => element.length > 1) .toList(); - return numberArr.join(andFa).trim(); + return trim(numberArr.join(andFa)); } /// Returns Persian word of the given number in int You can determine