numbers_in_words 0.1.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +14 -0
  3. data/.gitignore +7 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +58 -0
  6. data/.travis.yml +15 -0
  7. data/Gemfile +8 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +47 -0
  10. data/Rakefile +2 -43
  11. data/bin/spec +2 -0
  12. data/lib/numbers_in_words.rb +55 -4
  13. data/lib/numbers_in_words/duck_punch.rb +23 -0
  14. data/lib/numbers_in_words/exceptional_numbers.rb +115 -0
  15. data/lib/numbers_in_words/fraction.rb +136 -0
  16. data/lib/numbers_in_words/number_group.rb +64 -0
  17. data/lib/numbers_in_words/parsing/fraction_parsing.rb +34 -0
  18. data/lib/numbers_in_words/parsing/number_parser.rb +98 -0
  19. data/lib/numbers_in_words/parsing/pair_parsing.rb +64 -0
  20. data/lib/numbers_in_words/parsing/parse_fractions.rb +45 -0
  21. data/lib/numbers_in_words/parsing/parse_individual_number.rb +68 -0
  22. data/lib/numbers_in_words/parsing/parse_status.rb +17 -0
  23. data/lib/numbers_in_words/parsing/special.rb +67 -0
  24. data/lib/numbers_in_words/parsing/to_number.rb +77 -0
  25. data/lib/numbers_in_words/powers_of_ten.rb +49 -0
  26. data/lib/numbers_in_words/to_word.rb +84 -0
  27. data/lib/numbers_in_words/version.rb +5 -0
  28. data/lib/numbers_in_words/writer.rb +69 -0
  29. data/numbers_in_words.gemspec +20 -27
  30. data/spec/exceptional_numbers_spec.rb +26 -0
  31. data/spec/fraction_spec.rb +152 -0
  32. data/spec/fractions_spec.rb +31 -0
  33. data/spec/non_monkey_patch_spec.rb +51 -0
  34. data/spec/number_group_spec.rb +17 -0
  35. data/spec/number_parser_spec.rb +31 -0
  36. data/spec/numbers_in_words_spec.rb +69 -83
  37. data/spec/numerical_strings_spec.rb +35 -0
  38. data/spec/spec_helper.rb +26 -0
  39. data/spec/to_word_spec.rb +18 -0
  40. data/spec/words_in_numbers_spec.rb +137 -119
  41. data/spec/writer_spec.rb +26 -0
  42. data/spec/years_spec.rb +27 -0
  43. metadata +95 -45
  44. data/CHANGELOG +0 -1
  45. data/Manifest +0 -11
  46. data/README +0 -84
  47. data/examples/display_numbers_in_words.rb +0 -22
  48. data/init.rb +0 -8
  49. data/lib/numbers.rb +0 -260
  50. data/lib/words.rb +0 -221
data/CHANGELOG DELETED
@@ -1 +0,0 @@
1
- v0.1.0. Handles decimals
data/Manifest DELETED
@@ -1,11 +0,0 @@
1
- CHANGELOG
2
- Manifest
3
- README
4
- Rakefile
5
- examples/display_numbers_in_words.rb
6
- init.rb
7
- lib/numbers.rb
8
- lib/numbers_in_words.rb
9
- lib/words.rb
10
- spec/numbers_in_words_spec.rb
11
- spec/words_in_numbers_spec.rb
data/README DELETED
@@ -1,84 +0,0 @@
1
- Installation
2
- ============
3
-
4
- gem install numbers_in_words
5
-
6
- or
7
-
8
- sudo gem install numbers_in_words
9
-
10
-
11
-
12
-
13
- The file numbers_to_words defines a module NumbersToWords which is included in Fixnum and Bignum.
14
- The in_words method can then be used on any Fixnum or Bignum object.
15
-
16
- E.g.
17
- >require 'numbers_in_words'
18
- >112.in_words
19
- => one hundred and twelve
20
- >"one googol".in_numbers
21
- =>10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
22
- >"Seventy million, five-hundred and fifty six thousand point eight nine three".in_numbers
23
- => 70556000.893
24
-
25
- ---------------
26
- Example program
27
-
28
- To try it out the program display_numbers_to_words.rb expects two integer parameters to define a
29
- range. It then prints out all numbers in words included in this range.
30
-
31
- e.g.
32
-
33
- >ruby display_numbers_to_words.rb 1 11
34
- one
35
- two
36
- three
37
- four
38
- five
39
- six
40
- seven
41
- eight
42
- nine
43
- ten
44
- eleven
45
-
46
- Whilst creating this project I realized that in English:
47
-
48
- * Numbers are grouped in groups of threes
49
- * Numbers less than 1,000 are grouped by hundreds and then by tens
50
- * There are specific rules for when we put an "and" in between numbers
51
-
52
- It makes sense to manage the numbers by these groups, so
53
- I created a method groups_of which will split any integer into
54
- groups of a certain size. It returns a hash with the power of ten
55
- as the key and the multiplier as the value. E.g:
56
-
57
- > 31245.groups_of(3)
58
- #=>
59
- {0=>245,3=>31} #i.e. 31 thousands, and 245 ones
60
-
61
- > 245.group_of(2)
62
- #=>
63
- {0=>45,2=>2} #i.e. 2 hundreds, and 45 ones
64
-
65
- I also created a method group_words takes a block and a size parameter
66
- This method which could be used by different languages.
67
- (In Japanese numbers are grouped in groups of 4, so it makes sense to try and
68
- separate the language related stuff from the number grouping related stuff)
69
-
70
- Example of usage:
71
- 245.group_words(2,"English") do |power, name, digits|
72
- puts "#{digits}*10^#{power} #{digits} #{name}s"
73
- end
74
-
75
- 2*10^2= 2 hundreds
76
- 45*10^0 = 45 ones
77
-
78
- Future plans
79
- ============
80
-
81
- * Handle complex numbers
82
- * Option for outputting punctuation
83
- * Reject invalid numbers
84
- * Support for other languages
@@ -1,22 +0,0 @@
1
- require 'rubygems'
2
- require 'numbers_in_words'
3
- def usage code=-1
4
- puts "\nUsage:\n\nruby #{__FILE__} <start> <end>"
5
- exit code
6
- end
7
- if ARGV.length != 2
8
-
9
- puts "Start and end arguments expected"
10
- usage -1
11
- end
12
- start_number = ARGV[0].to_i
13
- end_number = ARGV[1].to_i
14
-
15
- if start_number > end_number
16
- puts "Start number must be less than or equal to the end number"
17
- usage -2
18
- end
19
-
20
- for i in start_number..end_number do
21
- puts i.in_words
22
- end
data/init.rb DELETED
@@ -1,8 +0,0 @@
1
- require 'rubygems'
2
-
3
- $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
4
-
5
- require 'lib/numbers_in_words'
6
- require 'lib/words_in_numbers'
7
-
8
-
@@ -1,260 +0,0 @@
1
- require 'rubygems'
2
- require 'active_support/core_ext/array'
3
- module NumbersInWords
4
- EXCEPTIONS = {
5
- 10 => "ten",
6
- 11 => "eleven",
7
- 12 => "twelve",
8
-
9
- 13 => "thirteen",
10
- 14 => "fourteen",
11
- 15 => "fifteen",
12
- 16 => "sixteen" ,
13
- 17 => "seventeen",
14
- 18 => "eighteen",
15
- 19 => "nineteen",
16
-
17
- 20 => "twenty",
18
- 30 => "thirty",
19
- 40 => "forty",
20
- 50 => "fifty",
21
- 60 => "sixty",
22
- 70 => "seventy",
23
- 80 => "eighty",
24
- 90 => "ninety"
25
- }
26
-
27
- DIGITS = %w[zero one two three four five six seven eight nine]
28
-
29
- POWERS_OF_TEN ={
30
- 0 => "one",
31
- 1 => "ten",
32
- 2 => "hundred",
33
- 3 => "thousand",
34
- 6 => "million",
35
- 9 => "billion",
36
- 12 => "trillion",
37
- 15 => "quadrillion",
38
- 18 => "quintillion",
39
- 21 => "sextillion",
40
- 24 => "septillion",
41
- 27 => "octillion",
42
- 30 => "nonillion",
43
- 33 => "decillion",
44
- 36 => "undecillion",
45
- 39 => "duodecillion",
46
- 42 => "tredecillion",
47
- 45 => "quattuordecillion",
48
- 48 => "quindecillion",
49
- 51 => "sexdecillion",
50
- 54 => "septendecillion",
51
- 57 => "octodecillion",
52
- 60 => "novemdecillion",
53
- 63 => "vigintillion",
54
- 66 => "unvigintillion",
55
- 69 => "duovigintillion",
56
- 72 => "trevigintillion",
57
- 75 => "quattuorvigintillion",
58
- 78 => "quinvigintillion",
59
- 81 => "sexvigintillion",
60
- 84 => "septenvigintillion",
61
- 87 => "octovigintillion",
62
- 90 => "novemvigintillion",
63
- 93 => "trigintillion",
64
- 96 => "untrigintillion",
65
- 99 => "duotrigintillion",
66
- 100 => "googol"
67
- }
68
-
69
- LENGTH_OF_GOOGOL = 101 #length of the string i.e. one with 100 zeros
70
-
71
- def in_words language="English"
72
- case language
73
- when "English" #allow for I18n
74
- in_english
75
- end
76
- end
77
-
78
- def in_english
79
- #handle 0-9
80
- return DIGITS[self] if self.is_a?(Integer) and (0..9).to_a.include? self
81
- #teens etc
82
- return EXCEPTIONS[self] if self.is_a?(Integer) and EXCEPTIONS[self]
83
-
84
- writer = LanguageWriterEnglish.new(self)
85
-
86
- in_decimals = writer.decimals
87
- return in_decimals unless in_decimals.nil?
88
-
89
- number = to_i
90
-
91
- return writer.negative() if number < 0
92
-
93
- length = number.to_s.length
94
- output = ""
95
-
96
- if length == 2 #20-99
97
- tens = (number/10).round*10 #write the tens
98
-
99
- output << EXCEPTIONS[tens] # e.g. eighty
100
-
101
- digit = number - tens #write the digits
102
-
103
- output << " " + digit.in_english unless digit==0
104
- else
105
- output << writer.write() #longer numbers
106
- end
107
-
108
- output.strip
109
- end
110
-
111
- protected
112
-
113
- class LanguageWriter
114
- attr_accessor :number
115
-
116
- def initialize number
117
- @number = number
118
- end
119
-
120
- def group_words size
121
- #1000 and over Numbers are split into groups of three
122
- groups = NumberGroup.groups_of @number, size
123
- powers = groups.keys.sort.reverse #put in descending order
124
-
125
- powers.each do |power|
126
- name = POWERS_OF_TEN[power]
127
- digits = groups[power]
128
- yield power, name, digits
129
- end
130
- end
131
- end
132
-
133
- class LanguageWriterEnglish < LanguageWriter
134
- def negative
135
- "minus " + (-@number).in_english
136
- end
137
-
138
- def write
139
- length = @number.to_s.length
140
- output = if length == 3
141
- #e.g. 113 splits into "one hundred" and "thirteen"
142
- write_groups(2)
143
-
144
- #more than one hundred less than one googol
145
- elsif length < LENGTH_OF_GOOGOL
146
- write_groups(3)
147
-
148
- elsif length >= LENGTH_OF_GOOGOL
149
- write_googols
150
-
151
- end
152
- output.strip
153
- end
154
-
155
- def decimals
156
- int, decimals = NumberGroup.new(@number).split_decimals
157
- if int
158
- out = int.in_english + " point "
159
- decimals.each do |decimal|
160
- out << decimal.to_i.in_english + " "
161
- end
162
- out.strip
163
- end
164
- end
165
-
166
- private
167
- def write_googols
168
- googols, remainder = NumberGroup.new(@number).split_googols
169
- output = ""
170
- output << " " + googols.in_words + " googol"
171
- if remainder > 0
172
- prefix = " "
173
- prefix << "and " if remainder < 100
174
- output << prefix + remainder.in_english
175
- end
176
- output
177
- end
178
-
179
- def write_groups group
180
- #e.g. 113 splits into "one hundred" and "thirteen"
181
- output = ""
182
- group_words(group) do |power, name, digits|
183
- if digits > 0
184
- prefix = " "
185
- #no and between thousands and hundreds
186
- prefix << "and " if power == 0 and digits < 100
187
- output << prefix + digits.in_english
188
- output << prefix + name unless power == 0
189
- end
190
- end
191
- output
192
- end
193
- end
194
-
195
- class NumberGroup
196
- include Enumerable
197
- attr_accessor :number
198
-
199
- def each
200
- @array.each { |item| yield item}
201
- end
202
-
203
- #split into groups this gives us 1234567 => 123 456 7
204
- #so we need to reverse first
205
- #in stages
206
- def initialize number
207
- @number = number
208
- end
209
-
210
- def groups size
211
- #i.e. 1234567 => 7654321
212
- groups = @number.to_s.reverse
213
- #7654321 => 765 432 1
214
- @array = groups.split("").in_groups_of(size)
215
- #765 432 1 => 1 432 765
216
- @array.reverse!
217
- #1 432 765 => 1 234 567
218
- #and turn back into integers
219
- @array.map! {|group| group.reverse.join("").to_i}
220
- @array.reverse! # put in ascending order of power of ten
221
- power = 0
222
- output = @array.inject({}) do |output, digits|
223
- output[power] = digits
224
- power += size
225
- output
226
- end
227
- output
228
- end
229
-
230
- def split_decimals
231
- if @number.is_a? Float
232
- int = @number.to_i
233
- decimal = @number - int
234
- decimal = decimal.to_s.split(".")[1]
235
- digits = decimal.split //
236
- #convert to integers array
237
- digits.inject([]) {|out, digit|
238
- out<< digit.to_i
239
- }
240
- return int, digits
241
- end
242
- end
243
-
244
- def self.groups_of number, size
245
- new(number).groups(size)
246
- end
247
-
248
- def split_googols
249
- output = ""
250
- googols = @number.to_s[0 .. (-LENGTH_OF_GOOGOL)].to_i
251
- remainder = @number.to_s[(1-LENGTH_OF_GOOGOL) .. -1].to_i
252
- return googols, remainder
253
- end
254
- end
255
- end
256
-
257
-
258
- class Numeric
259
- include NumbersInWords
260
- end
@@ -1,221 +0,0 @@
1
- module WordsInNumbers
2
- def in_numbers
3
- text = to_s
4
-
5
- WordToNumber.new.instance_eval do
6
- text = strip_punctuation text
7
- #negative numbers
8
- return -1 * (text.gsub(/^minus /, "")).in_numbers if text =~ /^minus /
9
-
10
- #easy single word case
11
- word = word_to_integer(text)
12
- return word unless word.nil?
13
-
14
- #decimals
15
- match = text.match(/\spoint\s/)
16
- if match
17
- integer = match.pre_match.in_numbers
18
-
19
- decimal = decimal_portion match.post_match
20
-
21
- return integer + decimal
22
- end
23
-
24
- #multiple word case
25
- words = text.split " "
26
- integers = word_array_to_integers words
27
-
28
- integer= parse_numbers(integers)
29
- return integer unless integer.nil?
30
- return nil
31
- end
32
- end
33
-
34
- class WordToNumber
35
- DIGITS= %w[zero one two three four five six seven eight nine]
36
-
37
- EXCEPTIONS = {
38
- "ten" => 10,
39
- "eleven" => 11,
40
- "twelve" => 12,
41
- "thirteen" => 13,
42
- "fourteen" => 14,
43
- "fifteen" => 15,
44
- "sixteen" => 16,
45
- "seventeen" => 17,
46
- "eighteen" => 18,
47
- "nineteen" => 19,
48
- "twenty" => 20,
49
- "thirty" => 30,
50
- "forty" => 40,
51
- "fifty" => 50,
52
- "sixty" => 60,
53
- "seventy" => 70,
54
- "eighty" => 80,
55
- "ninety" => 90
56
- }
57
-
58
-
59
- POWERS_OF_TEN ={
60
- "one" => 0,
61
- "ten" => 1 ,
62
- "hundred" => 2,
63
- "thousand" => 3 ,
64
- "million" => 6,
65
- "billion" => 9,
66
- "trillion" => 12,
67
- "quadrillion" => 15,
68
- "quintillion" => 18,
69
- "sextillion" => 21,
70
- "septillion" => 24,
71
- "octillion" => 27,
72
- "nonillion" => 30,
73
- "decillion" => 33,
74
- "undecillion" => 36,
75
- "duodecillion" => 39,
76
- "tredecillion" => 42,
77
- "quattuordecillion" => 45,
78
- "quindecillion" => 48,
79
- "sexdecillion" => 51,
80
- "septendecillion" => 54,
81
- "octodecillion" => 57,
82
- "novemdecillion" => 60,
83
- "vigintillion" => 63,
84
- "unvigintillion" => 66,
85
- "duovigintillion" => 69,
86
- "trevigintillion" => 72,
87
- "quattuorvigintillion" => 75,
88
- "quinvigintillion" => 78,
89
- "sexvigintillion" => 81,
90
- "septenvigintillion" => 84,
91
- "octovigintillion" => 87,
92
- "novemvigintillion" => 90,
93
- "trigintillion" => 93,
94
- "untrigintillion" => 96,
95
- "duotrigintillion" => 99,
96
- "googol" => 100
97
- }
98
-
99
- def strip_punctuation text
100
- text = text.downcase.gsub /[^a-z ]/, " "
101
- to_remove = true
102
-
103
- to_remove = text.gsub! " ", " " while to_remove
104
-
105
- text
106
- end
107
-
108
- def decimal_portion text
109
- words = text.split " "
110
- integers = word_array_to_integers words
111
- decimal = "0." + integers.join()
112
- decimal.to_f
113
- end
114
-
115
-
116
- private
117
-
118
- # Example: 364,895,457,898
119
- #three hundred and sixty four billion eight hundred and ninety five million
120
- #four hundred and fifty seven thousand eight hundred and ninety eight
121
- #
122
- #3 100 60 4 10^9, 8 100 90 5 10^6, 4 100 50 7 1000, 8 100 90 8
123
- # memory answer
124
- #x1. 3 add to memory because answer and memory are zero 3 0
125
- #x2. memory * 100 (because memory<100) 300 0
126
- #x3. 60 add to memory because memory > 60 360 0
127
- #x3. 4 add to memory because memory > 4 364 0
128
- #x4. multiply memory by 10^9 because memory < power of ten 364*10^9 0
129
- #x5. add memory to answer (and reset)memory > 8 (memory pow of ten > 2) 0 364*10^9
130
- #x6. 8 add to memory because not finished 8 ''
131
- #x7. multiply memory by 100 because memory < 100 800 ''
132
- #x8. add 90 to memory because memory > 90 890 ''
133
- #x9. add 5 to memory because memory > 5 895 ''
134
- #x10. multiply memory by 10^6 because memory < power of ten 895*10^6 ''
135
- #x11. add memory to answer (and reset) because memory power ten > 2 0 364895 * 10^6
136
- #x12. 4 add to memory because not finished 4 ''
137
- #x13. memory * 100 because memory < 100 400 ''
138
- #x14. memory + 50 because memory > 50 450 ''
139
- #x15. memory + 7 because memory > 7 457 ''
140
- #x16. memory * 1000 because memory < 1000 457000 ''
141
- #x17. add memory to answer (and reset)memory > 8 (memory pow of ten > 2) 0 364895457000
142
- #x18. 8 add to memory because not finished 8 ''
143
- #x19. memory * 100 because memory < 100 800 ''
144
- #x14. memory + 90 because memory > 90 890 ''
145
- #x15. memory + 8 because memory > 8 898 ''
146
- #16. finished so add memory to answer
147
-
148
- #Example
149
- #2001
150
- #two thousand and one
151
- #2 1000 1
152
- # memory answer
153
- #1. add 2 to memory because first 2 0
154
- #2. multiply memory by 1000 because memory < 1000 2000 0
155
- #3. add memory to answer,reset, because power of ten>2 0 2000
156
- #4. add 1 to memory 1 2000
157
- #5. finish - add memory to answer 0 2001
158
- def parse_numbers(integers)
159
- memory = 0
160
- answer = 0
161
- reset = true #reset each time memory is reset
162
- integers.each_with_index do |integer, index|
163
- if reset
164
- reset = false
165
- memory += integer
166
- else
167
- #x4. multiply memory by 10^9 because memory < power of ten
168
- if is_power_of_ten?(integer)
169
- if power_of_ten(integer)> 2
170
- memory *= integer
171
- #17. add memory to answer (and reset) (memory pow of ten > 2)
172
- answer += memory
173
- memory = 0
174
- reset = true
175
- end
176
- end
177
-
178
- if memory < integer
179
- memory *= integer
180
- else
181
- memory += integer
182
- end
183
- end
184
- end
185
- answer += memory
186
- end
187
-
188
- def power_of_ten integer
189
- Math.log10(integer)
190
- end
191
-
192
- def is_power_of_ten? integer
193
- power_of_ten(integer)==power_of_ten(integer).to_i
194
- end
195
-
196
- #handles simple single word numbers
197
- #e.g. one, seven, twenty, eight, thousand etc
198
- def word_to_integer word
199
- text = word.to_s.chomp.strip
200
- #digits 0-9
201
- digit = DIGITS.index(text)
202
- return digit unless digit.nil?
203
-
204
- #digits which are exceptions
205
- exception = EXCEPTIONS[text]
206
- return exception unless exception.nil?
207
-
208
- power = POWERS_OF_TEN[text]
209
- return 10**power unless power.nil?
210
- end
211
-
212
- def word_array_to_integers words
213
- words.map { |i| word_to_integer i }.compact
214
- end
215
- end
216
- end
217
-
218
- class String
219
- include WordsInNumbers
220
- end
221
-