numbers_in_words 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codeclimate.yml +2 -0
- data/README.md +4 -3
- data/bin/spec +0 -0
- data/lib/numbers_in_words.rb +4 -4
- data/lib/numbers_in_words/duck_punch.rb +2 -2
- data/lib/numbers_in_words/english/constants.rb +31 -0
- data/lib/numbers_in_words/english/language_writer_english.rb +16 -9
- data/lib/numbers_in_words/language_writer.rb +0 -1
- data/lib/numbers_in_words/number_group.rb +2 -6
- data/lib/numbers_in_words/number_parser.rb +57 -3
- data/lib/numbers_in_words/to_number.rb +29 -23
- data/lib/numbers_in_words/version.rb +1 -1
- data/numbers_in_words.gemspec +2 -2
- data/spec/non_monkey_patch_spec.rb +6 -0
- data/spec/numerical_strings_spec.rb +19 -0
- data/spec/spec_helper.rb +3 -2
- data/spec/words_in_numbers_spec.rb +10 -0
- data/spec/years_spec.rb +17 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2857f0873d094b51ce7ca5e4a90ac8e999bebb6
|
4
|
+
data.tar.gz: b0e885874e36c119b69290ae0bb30110419f7031
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98b44efc9e3c5bd28d5f3f26d11fd4b62f12fde010ce4ca30fe35e57ca10a8cb0822ac50fcb977a4823d7c722a75c71c670b52584c5361c2aae8fe2527ff95a9
|
7
|
+
data.tar.gz: 99b23e8c07dea648e1afc393f28f4b8981594c30a05bb4970b616cd76098294ddaf7b450bf683dd1656e8cadf01573b7fcf86164e32d12afbba95adde26e31ed
|
data/.codeclimate.yml
CHANGED
data/README.md
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
[](https://travis-ci.org/markburns/numbers_in_words)
|
2
2
|
[](https://gemnasium.com/markburns/numbers_in_words)
|
3
3
|
[](https://codeclimate.com/github/markburns/numbers_in_words)
|
4
|
+
[](https://codeclimate.com/github/markburns/numbers_in_words/coverage)
|
4
5
|
[](https://rubygems.org/gems/numbers_in_words)
|
5
6
|
[](http://markburns.mit-license.org)
|
6
|
-
[](https://github.com/badges/badgerbadgerbadger)
|
7
8
|
|
8
9
|
Installation
|
9
10
|
============
|
@@ -12,7 +13,7 @@ Installation
|
|
12
13
|
gem 'numbers_in_words'
|
13
14
|
|
14
15
|
require 'numbers_in_words'
|
15
|
-
require 'numbers_in_words/duck_punch' #see why later
|
16
|
+
require 'numbers_in_words/duck_punch' #optional see why later
|
16
17
|
```
|
17
18
|
|
18
19
|
This project was created for a test for a job interview. I haven't really used
|
@@ -27,7 +28,7 @@ require 'numbers_in_words'
|
|
27
28
|
NumbersInWords.in_words(112)
|
28
29
|
#=> one hundred and twelve
|
29
30
|
|
30
|
-
NumbersInWords.in_numbers(
|
31
|
+
NumbersInWords.in_numbers("one googol")
|
31
32
|
#=>10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
32
33
|
|
33
34
|
NumbersInWords.in_numbers("Seventy million, five-hundred and fifty six thousand point eight nine three")
|
data/bin/spec
CHANGED
File without changes
|
data/lib/numbers_in_words.rb
CHANGED
@@ -21,12 +21,12 @@ module NumbersInWords
|
|
21
21
|
@language ||= "English"
|
22
22
|
end
|
23
23
|
|
24
|
-
def in_words(i, language=NumbersInWords.language)
|
25
|
-
NumbersInWords::ToWord.new(i, language).in_words
|
24
|
+
def in_words(i, language=NumbersInWords.language, only_compress=false)
|
25
|
+
NumbersInWords::ToWord.new(i, language).in_words(only_compress)
|
26
26
|
end
|
27
27
|
|
28
|
-
def in_numbers(s, language=NumbersInWords.language)
|
29
|
-
NumbersInWords::ToNumber.new(s, language).in_numbers
|
28
|
+
def in_numbers(s, language=NumbersInWords.language, only_compress=false)
|
29
|
+
NumbersInWords::ToNumber.new(s, language).in_numbers(only_compress)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -5,8 +5,8 @@ module NumbersInWords
|
|
5
5
|
end
|
6
6
|
|
7
7
|
module WordsInNumbers
|
8
|
-
def in_numbers language=NumbersInWords.language
|
9
|
-
NumbersInWords.
|
8
|
+
def in_numbers(only_compress = false, language=NumbersInWords.language)
|
9
|
+
NumbersInWords::ToNumber.new(self, language).in_numbers(only_compress)
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
@@ -1,5 +1,14 @@
|
|
1
1
|
module NumbersInWords
|
2
2
|
module English
|
3
|
+
|
4
|
+
def self.canonize(w)
|
5
|
+
aliases = {
|
6
|
+
"oh" => "zero"
|
7
|
+
}
|
8
|
+
canon = aliases[w]
|
9
|
+
return canon ? canon : w
|
10
|
+
end
|
11
|
+
|
3
12
|
def self.exceptions
|
4
13
|
{
|
5
14
|
0 => "zero",
|
@@ -89,5 +98,27 @@ module NumbersInWords
|
|
89
98
|
def self.powers_of_ten_to_i
|
90
99
|
swap_keys powers_of_ten
|
91
100
|
end
|
101
|
+
|
102
|
+
POWERS_RX = Regexp.union(powers_of_ten.values[1..-1])
|
103
|
+
|
104
|
+
def self.check_mixed(txt)
|
105
|
+
mixed = txt.match /^(-?\d+(.\d+)?) (#{POWERS_RX}s?)$/
|
106
|
+
if mixed && mixed[1] && mixed[3]
|
107
|
+
matches = [mixed[1], mixed[3]].map{ |m| NumbersInWords.in_numbers m }
|
108
|
+
return matches.reduce(&:*)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.check_one(txt)
|
113
|
+
one = txt.match /^one (#{POWERS_RX})$/
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.strip_minus(txt)
|
117
|
+
stripped = txt.gsub(/^minus/, "") if txt =~ /^minus/
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.check_decimal(txt)
|
121
|
+
txt.match(/\spoint\s/)
|
122
|
+
end
|
92
123
|
end
|
93
124
|
end
|
@@ -23,22 +23,29 @@ module NumbersInWords
|
|
23
23
|
|
24
24
|
return negative() if number < 0
|
25
25
|
|
26
|
-
|
26
|
+
output = if number.to_s.length == 2 #20-99
|
27
|
+
handle_tens(number)
|
28
|
+
else
|
29
|
+
write() #longer numbers
|
30
|
+
end
|
31
|
+
|
32
|
+
output.strip
|
33
|
+
end
|
34
|
+
|
35
|
+
def handle_tens(number)
|
27
36
|
output = ""
|
28
37
|
|
29
|
-
|
30
|
-
tens = (number/10).round*10 #write the tens
|
38
|
+
tens = (number/10).round*10 #write the tens
|
31
39
|
|
32
|
-
|
40
|
+
output << exceptions[tens] # e.g. eighty
|
33
41
|
|
34
|
-
|
42
|
+
digit = number - tens #write the digits
|
35
43
|
|
36
|
-
|
37
|
-
|
38
|
-
output << write() #longer numbers
|
44
|
+
unless digit == 0
|
45
|
+
output << " " + NumbersInWords.in_words(digit)
|
39
46
|
end
|
40
47
|
|
41
|
-
output
|
48
|
+
output
|
42
49
|
end
|
43
50
|
|
44
51
|
def handle_exception
|
@@ -3,8 +3,8 @@ module NumbersInWords
|
|
3
3
|
include Enumerable
|
4
4
|
attr_accessor :number
|
5
5
|
|
6
|
-
def
|
7
|
-
|
6
|
+
def self.groups_of number, size
|
7
|
+
new(number).groups(size)
|
8
8
|
end
|
9
9
|
|
10
10
|
def initialize number
|
@@ -42,10 +42,6 @@ module NumbersInWords
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
def self.groups_of number, size
|
46
|
-
new(number).groups(size)
|
47
|
-
end
|
48
|
-
|
49
45
|
def split_googols
|
50
46
|
googols = @number.to_s[0 .. (-LENGTH_OF_GOOGOL)].to_i
|
51
47
|
remainder = @number.to_s[(1-LENGTH_OF_GOOGOL) .. -1].to_i
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module NumbersInWords::NumberParser
|
2
|
+
SCALES_N = [100, 1000, 1000000, 1000000000, 1000000000000, 10**100]
|
2
3
|
# Example: 364,895,457,898
|
3
4
|
#three hundred and sixty four billion eight hundred and ninety five million
|
4
5
|
#four hundred and fifty seven thousand eight hundred and ninety eight
|
@@ -39,7 +40,7 @@ module NumbersInWords::NumberParser
|
|
39
40
|
#3. add memory to answer,reset, because power of ten>2 0 2000
|
40
41
|
#4. add 1 to memory 1 2000
|
41
42
|
#5. finish - add memory to answer 0 2001
|
42
|
-
def
|
43
|
+
def parse_ints(integers)
|
43
44
|
memory = 0
|
44
45
|
answer = 0
|
45
46
|
reset = true #reset each time memory is reset
|
@@ -58,8 +59,7 @@ module NumbersInWords::NumberParser
|
|
58
59
|
reset = true
|
59
60
|
end
|
60
61
|
end
|
61
|
-
|
62
|
-
if memory < integer
|
62
|
+
if memory < integer
|
63
63
|
memory *= integer
|
64
64
|
else
|
65
65
|
memory += integer
|
@@ -69,6 +69,18 @@ module NumbersInWords::NumberParser
|
|
69
69
|
answer += memory
|
70
70
|
end
|
71
71
|
|
72
|
+
def parse(integers, only_compress = false)
|
73
|
+
if integers.length < 2
|
74
|
+
return integers if only_compress
|
75
|
+
return integers.empty? ? 0 : integers[0]
|
76
|
+
end
|
77
|
+
if [] == (SCALES_N & integers)
|
78
|
+
return pair_parse(integers, only_compress)
|
79
|
+
end
|
80
|
+
|
81
|
+
parse_ints(integers)
|
82
|
+
end
|
83
|
+
|
72
84
|
def power_of_ten integer
|
73
85
|
Math.log10(integer)
|
74
86
|
end
|
@@ -77,5 +89,47 @@ module NumbersInWords::NumberParser
|
|
77
89
|
power_of_ten(integer) == power_of_ten(integer).to_i
|
78
90
|
end
|
79
91
|
|
92
|
+
# 15,16
|
93
|
+
# 85,16
|
94
|
+
def pair_parse(ints, only_compress = false)
|
95
|
+
ints = compress(ints)
|
96
|
+
return ints if only_compress
|
97
|
+
return ints[0] if ints.length == 1
|
98
|
+
sum = 0
|
99
|
+
ints.each do |n|
|
100
|
+
sum *= n >= 10 ? 100 : 10
|
101
|
+
sum += n
|
102
|
+
end
|
103
|
+
sum
|
104
|
+
end
|
105
|
+
|
106
|
+
# [40, 2] => [42]
|
107
|
+
def compress(ints)
|
108
|
+
res = []
|
109
|
+
i = 0
|
110
|
+
return [] if ints.empty?
|
111
|
+
while i < ints.length - 1
|
112
|
+
int, jump = compress_int(ints[i], ints[i + 1])
|
113
|
+
res << int
|
114
|
+
i += jump
|
115
|
+
end
|
116
|
+
if i < ints.length
|
117
|
+
res << ints[-1]
|
118
|
+
else
|
119
|
+
res
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def compress_int(int, sequel)
|
124
|
+
tens = int % 10 == 0 && int > 10
|
125
|
+
if tens && sequel < 10
|
126
|
+
return [int + sequel, 2]
|
127
|
+
else
|
128
|
+
return [int, 1]
|
129
|
+
end
|
130
|
+
|
131
|
+
[res, jump]
|
132
|
+
end
|
133
|
+
|
80
134
|
extend self
|
81
135
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
class NumbersInWords::ToNumber
|
2
2
|
delegate :to_s, to: :that
|
3
|
-
delegate :powers_of_ten_to_i, :exceptions_to_i,
|
3
|
+
delegate :powers_of_ten_to_i, :exceptions_to_i, :canonize, \
|
4
|
+
:check_mixed, :check_one, :strip_minus, :check_decimal, to: :language
|
4
5
|
attr_reader :that, :language
|
5
6
|
|
6
7
|
def initialize that, language=NumbersInWords.language
|
@@ -16,27 +17,42 @@ class NumbersInWords::ToNumber
|
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
19
|
-
def handle_negative
|
20
|
-
|
20
|
+
def handle_negative(text, only_compress)
|
21
|
+
stripped = strip_minus text
|
22
|
+
if stripped
|
23
|
+
stripped_n = NumbersInWords.in_numbers(stripped, language, only_compress)
|
24
|
+
only_compress ? stripped_n.map{ |k| k * -1 } : -1 * stripped_n
|
25
|
+
end
|
21
26
|
end
|
22
27
|
|
23
|
-
def in_numbers
|
24
|
-
text = to_s
|
28
|
+
def in_numbers(only_compress = false)
|
29
|
+
text = to_s.strip
|
30
|
+
return text.to_f if text =~ /^-?\d+(.\d+)?$/
|
25
31
|
|
26
32
|
text = strip_punctuation text
|
27
|
-
|
33
|
+
|
34
|
+
i = handle_negative(text, only_compress)
|
28
35
|
return i if i
|
29
36
|
|
37
|
+
mixed = check_mixed text
|
38
|
+
return mixed if mixed
|
39
|
+
|
40
|
+
one = check_one text
|
41
|
+
if one
|
42
|
+
res = NumbersInWords.in_numbers(one[1], language)
|
43
|
+
return only_compress ? [res] : res
|
44
|
+
end
|
45
|
+
|
30
46
|
h = handle_decimals text
|
31
47
|
return h if h
|
32
48
|
|
33
49
|
integers = word_array_to_integers text.split(" ")
|
34
50
|
|
35
|
-
NumbersInWords::NumberParser.parse integers
|
51
|
+
NumbersInWords::NumberParser.parse integers, only_compress
|
36
52
|
end
|
37
53
|
|
38
54
|
def strip_punctuation text
|
39
|
-
text = text.downcase.gsub(/[^a-z ]/, " ")
|
55
|
+
text = text.downcase.gsub(/[^a-z 0-9]/, " ")
|
40
56
|
to_remove = true
|
41
57
|
|
42
58
|
to_remove = text.gsub! " ", " " while to_remove
|
@@ -45,28 +61,18 @@ class NumbersInWords::ToNumber
|
|
45
61
|
end
|
46
62
|
|
47
63
|
def handle_decimals text
|
48
|
-
match = text
|
64
|
+
match = check_decimal text
|
49
65
|
if match
|
50
|
-
integer = match.pre_match
|
51
|
-
|
52
|
-
decimal
|
53
|
-
|
54
|
-
integer + decimal
|
66
|
+
integer = NumbersInWords.in_numbers(match.pre_match)
|
67
|
+
decimal = NumbersInWords.in_numbers(match.post_match)
|
68
|
+
integer += "0.#{decimal}".to_f
|
55
69
|
end
|
56
70
|
end
|
57
71
|
|
58
|
-
|
59
|
-
def decimal_portion text
|
60
|
-
words = text.split " "
|
61
|
-
integers = word_array_to_integers words
|
62
|
-
decimal = "0." + integers.join()
|
63
|
-
decimal.to_f
|
64
|
-
end
|
65
|
-
|
66
72
|
#handles simple single word numbers
|
67
73
|
#e.g. one, seven, twenty, eight, thousand etc
|
68
74
|
def word_to_integer word
|
69
|
-
text = word.to_s.chomp.strip
|
75
|
+
text = canonize(word.to_s.chomp.strip)
|
70
76
|
|
71
77
|
exception = exceptions_to_i[text]
|
72
78
|
return exception if exception
|
data/numbers_in_words.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |gem|
|
|
9
9
|
gem.summary = "Example: 123.in_words # => \"one hundred and twenty three\", \"seventy-five point eight\".in_numbers # = > 75.8"
|
10
10
|
|
11
11
|
gem.version = NumbersInWords::VERSION
|
12
|
-
gem.authors = ["Mark Burns"]
|
13
|
-
gem.email = ["markthedeveloper@gmail.com"]
|
12
|
+
gem.authors = ["Mark Burns", "Dimid Duchovny"]
|
13
|
+
gem.email = ["markthedeveloper@gmail.com", "dimidd@gmail.com"]
|
14
14
|
gem.homepage = "http://github.com/markburns/numbers_in_words"
|
15
15
|
|
16
16
|
gem.add_dependency "activesupport"
|
@@ -19,6 +19,12 @@ describe "NumbersInWords" do
|
|
19
19
|
describe ".in_numbers" do
|
20
20
|
it do
|
21
21
|
expect(NumbersInWords.in_numbers("one hundred")).to eq 100
|
22
|
+
|
23
|
+
expect(NumbersInWords.in_numbers("minus one hundred")).to eq -100
|
24
|
+
expect(NumbersInWords.in_numbers("twenty four" )).to eq 24
|
25
|
+
expect(NumbersInWords.in_numbers("one point two")).to eq 1.2
|
26
|
+
expect(NumbersInWords.in_numbers("one hundred googol")).to eq 100*10**100
|
27
|
+
expect(NumbersInWords.in_numbers("one hundred googol and thirty")).to eq 30 + 100*10**100
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe NumbersInWords do
|
4
|
+
it "should recognize numerical strings" do
|
5
|
+
arr = %w(8 56 100 5789 3435356)
|
6
|
+
arr.each{ |s| expect(s.in_numbers).to eql(s.to_f) }
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should recognize mixed strings" do
|
10
|
+
mixed = {
|
11
|
+
"19 hundred" => 1_900.0,
|
12
|
+
"20 thousand" => 20_000.0,
|
13
|
+
"100 million" => 100_000_000.0,
|
14
|
+
"7 billion" => 7_000_000_000.0,
|
15
|
+
"42 trillion" => 42_000_000_000_000.0
|
16
|
+
}
|
17
|
+
mixed.each{ |k, v| expect(k.in_numbers).to eql(v) }
|
18
|
+
end
|
19
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -132,4 +132,14 @@ describe WordsInNumbers do
|
|
132
132
|
in_numbers).to eq(75.84327694518)
|
133
133
|
end
|
134
134
|
|
135
|
+
it "should handle years notation" do
|
136
|
+
expect("fifteen sixteen".in_numbers) .to eq(1516)
|
137
|
+
expect("eighty five sixteen".in_numbers) .to eq(8516)
|
138
|
+
expect("nineteen ninety six".in_numbers) .to eq(1996)
|
139
|
+
expect("forty nine ninety eight forty seven seventy nine".in_numbers) .to eq(49984779)
|
140
|
+
expect("fifteen sixteen".in_numbers) .to eq(1516)
|
141
|
+
expect("fifteen sixteen seven".in_numbers) .to eq(15167)
|
142
|
+
expect("fifteen sixteen seventeen".in_numbers) .to eq(151617)
|
143
|
+
end
|
144
|
+
|
135
145
|
end
|
data/spec/years_spec.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe WordsInNumbers do
|
4
|
+
it "should handle years notation" do
|
5
|
+
expect("fifteen sixteen seventeen".in_numbers).to eq(151617)
|
6
|
+
expect("forty nine ninety eight forty seven seventy nine".in_numbers).to eq(49984779)
|
7
|
+
expect("sixty seven six".in_numbers) .to eq(676)
|
8
|
+
expect("one fifty".in_numbers).to eq(150)
|
9
|
+
expect("two fifty".in_numbers).to eq(250)
|
10
|
+
expect("one point fifty six fifty seven".in_numbers).to eq(1.5657)
|
11
|
+
expect("one three forty seven".in_numbers).to eq(1347)
|
12
|
+
expect("one three five point forty seven".in_numbers).to eq(135.47)
|
13
|
+
expect("one ten sixty three".in_numbers).to eq(11063)
|
14
|
+
expect("one nineteen ten oh five".in_numbers).to eq(1191005)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: numbers_in_words
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Burns
|
8
|
+
- Dimid Duchovny
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2015-
|
12
|
+
date: 2015-12-02 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: activesupport
|
@@ -41,6 +42,7 @@ dependencies:
|
|
41
42
|
description: "#in_words method for integers and #in_numbers for strings"
|
42
43
|
email:
|
43
44
|
- markthedeveloper@gmail.com
|
45
|
+
- dimidd@gmail.com
|
44
46
|
executables:
|
45
47
|
- spec
|
46
48
|
extensions: []
|
@@ -71,8 +73,10 @@ files:
|
|
71
73
|
- spec/non_monkey_patch_spec.rb
|
72
74
|
- spec/number_group_spec.rb
|
73
75
|
- spec/numbers_in_words_spec.rb
|
76
|
+
- spec/numerical_strings_spec.rb
|
74
77
|
- spec/spec_helper.rb
|
75
78
|
- spec/words_in_numbers_spec.rb
|
79
|
+
- spec/years_spec.rb
|
76
80
|
homepage: http://github.com/markburns/numbers_in_words
|
77
81
|
licenses: []
|
78
82
|
metadata: {}
|
@@ -102,5 +106,7 @@ test_files:
|
|
102
106
|
- spec/non_monkey_patch_spec.rb
|
103
107
|
- spec/number_group_spec.rb
|
104
108
|
- spec/numbers_in_words_spec.rb
|
109
|
+
- spec/numerical_strings_spec.rb
|
105
110
|
- spec/spec_helper.rb
|
106
111
|
- spec/words_in_numbers_spec.rb
|
112
|
+
- spec/years_spec.rb
|