numbers_in_words 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +39 -0
- data/LICENSE.txt +22 -0
- data/README.md +97 -0
- data/Rakefile +1 -44
- data/lib/numbers_in_words.rb +21 -4
- data/lib/numbers_in_words/duck_punch.rb +19 -0
- data/lib/numbers_in_words/english/constants.rb +93 -0
- data/lib/numbers_in_words/english/language_writer_english.rb +107 -0
- data/lib/numbers_in_words/language_writer.rb +31 -0
- data/lib/numbers_in_words/number_group.rb +55 -0
- data/lib/numbers_in_words/number_parser.rb +81 -0
- data/lib/numbers_in_words/to_number.rb +82 -0
- data/lib/numbers_in_words/to_word.rb +19 -0
- data/lib/numbers_in_words/version.rb +3 -0
- data/numbers_in_words.gemspec +18 -26
- data/spec/language_writer_spec.rb +23 -0
- data/spec/number_group_spec.rb +17 -0
- data/spec/numbers_in_words_spec.rb +1 -35
- data/spec/spec_helper.rb +2 -0
- data/spec/words_in_numbers_spec.rb +1 -1
- metadata +83 -35
- data/CHANGELOG +0 -1
- data/Manifest +0 -11
- data/README +0 -84
- data/examples/display_numbers_in_words.rb +0 -22
- data/init.rb +0 -8
- data/lib/numbers.rb +0 -260
- data/lib/words.rb +0 -221
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
numbers_in_words (0.2.0)
|
5
|
+
activesupport
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activesupport (3.2.10)
|
11
|
+
i18n (~> 0.6)
|
12
|
+
multi_json (~> 1.0)
|
13
|
+
columnize (0.3.6)
|
14
|
+
debugger (1.2.3)
|
15
|
+
columnize (>= 0.3.1)
|
16
|
+
debugger-linecache (~> 1.1.1)
|
17
|
+
debugger-ruby_core_source (~> 1.1.5)
|
18
|
+
debugger-linecache (1.1.2)
|
19
|
+
debugger-ruby_core_source (>= 1.1.1)
|
20
|
+
debugger-ruby_core_source (1.1.6)
|
21
|
+
diff-lcs (1.1.3)
|
22
|
+
i18n (0.6.1)
|
23
|
+
multi_json (1.5.0)
|
24
|
+
rspec (2.12.0)
|
25
|
+
rspec-core (~> 2.12.0)
|
26
|
+
rspec-expectations (~> 2.12.0)
|
27
|
+
rspec-mocks (~> 2.12.0)
|
28
|
+
rspec-core (2.12.2)
|
29
|
+
rspec-expectations (2.12.1)
|
30
|
+
diff-lcs (~> 1.1.3)
|
31
|
+
rspec-mocks (2.12.1)
|
32
|
+
|
33
|
+
PLATFORMS
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
debugger
|
38
|
+
numbers_in_words!
|
39
|
+
rspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Mark Burns
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
Installation
|
2
|
+
============
|
3
|
+
|
4
|
+
```
|
5
|
+
gem 'numbers_in_words'
|
6
|
+
|
7
|
+
require 'numbers_in_words'
|
8
|
+
require 'numbers_in_words/duck_punch' #see why later
|
9
|
+
```
|
10
|
+
|
11
|
+
This project was created for a test for a job interview. I haven't really used
|
12
|
+
it myself, but I saw it mentioned somewhere so I thought I'd tidy it up a bit.
|
13
|
+
|
14
|
+
I'm going to hopefully preempt some support queries by predicting this will happen:
|
15
|
+
|
16
|
+
You've got one of:
|
17
|
+
|
18
|
+
```
|
19
|
+
NoMethodError: undefined method `in_words' for 123:Fixnum
|
20
|
+
NoMethodError: undefined method `in_numbers' for "123":String
|
21
|
+
```
|
22
|
+
|
23
|
+
Here's why
|
24
|
+
==========
|
25
|
+
|
26
|
+
Previous versions of this gem duckpunched Fixnum and String with a whole bunch
|
27
|
+
of methods. This gem will now only add methods if you specifically tell it to
|
28
|
+
with:
|
29
|
+
|
30
|
+
```
|
31
|
+
require 'numbers_in_words'
|
32
|
+
require 'numbers_in_words/duck_punch'
|
33
|
+
```
|
34
|
+
|
35
|
+
Plus it now only adds a single `#in_words` method to `Numeric` and an `#in_numbers`
|
36
|
+
method to `String` instead of a whole bunch of them.
|
37
|
+
|
38
|
+
Usage
|
39
|
+
=========
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'numbers_in_words'
|
43
|
+
require 'numbers_in_words/duck_punch'
|
44
|
+
112.in_words
|
45
|
+
#=> one hundred and twelve
|
46
|
+
|
47
|
+
"one googol".in_numbers
|
48
|
+
#=>10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
49
|
+
|
50
|
+
"Seventy million, five-hundred and fifty six thousand point eight nine three".in_numbers
|
51
|
+
#=> 70556000.893
|
52
|
+
```
|
53
|
+
|
54
|
+
---------------
|
55
|
+
|
56
|
+
Whilst creating this project I realized that in English:
|
57
|
+
|
58
|
+
* Numbers are grouped in groups of threes
|
59
|
+
* Numbers less than 1,000 are grouped by hundreds and then by tens
|
60
|
+
* There are specific rules for when we put an "and" in between numbers
|
61
|
+
|
62
|
+
It makes sense to manage the numbers by these groups, so
|
63
|
+
I created a method groups_of which will split any integer into
|
64
|
+
groups of a certain size. It returns a hash with the power of ten
|
65
|
+
as the key and the multiplier as the value. E.g:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
31245.groups_of(3)
|
69
|
+
#=> {0=>245,3=>31} #i.e. 31 thousands, and 245 ones
|
70
|
+
|
71
|
+
245.group_of(2)
|
72
|
+
#=> {0=>45,2=>2} #i.e. 2 hundreds, and 45 ones
|
73
|
+
```
|
74
|
+
|
75
|
+
(In Japanese numbers are grouped in groups of 4, so it makes sense to try and
|
76
|
+
separate the language related stuff from the number grouping related stuff)
|
77
|
+
|
78
|
+
Example of usage:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
245.group_words(2,"English") do |power, name, digits|
|
82
|
+
puts "#{digits}*10^#{power} #{digits} #{name}s"
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
2 * 10^2 = 2 hundreds
|
87
|
+
45 * 10^0 = 45 ones
|
88
|
+
|
89
|
+
```
|
90
|
+
|
91
|
+
Future plans
|
92
|
+
============
|
93
|
+
|
94
|
+
* Handle complex numbers
|
95
|
+
* Option for outputting punctuation
|
96
|
+
* Reject invalid numbers
|
97
|
+
* Support for other languages
|
data/Rakefile
CHANGED
@@ -1,44 +1 @@
|
|
1
|
-
require
|
2
|
-
require 'rake'
|
3
|
-
require 'echoe'
|
4
|
-
require 'metric_fu'
|
5
|
-
|
6
|
-
Echoe.new('numbers_in_words','0.1.1') do |e|
|
7
|
-
e.description = "#in_words method for integers and #in_numbers for strings"
|
8
|
-
e.summary = "Example: 123.in_words #=> \"one hundred and twenty three\", \"seventy-five point eight\".in_numbers #=> 75.8"
|
9
|
-
e.url = "http://rubygems.org/gems/numbers_in_words"
|
10
|
-
e.author = "Mark Burns"
|
11
|
-
e.email = "markthedeveloper@gmail.com"
|
12
|
-
e.ignore_pattern = ["tmp/*",".git/*"]
|
13
|
-
e.dependencies = ['active_support']
|
14
|
-
end
|
15
|
-
|
16
|
-
|
17
|
-
MetricFu::Configuration.run do |config|
|
18
|
-
#define which metrics you want to use
|
19
|
-
config.metrics = [:churn, :saikuro, :stats, :flog, :flay, :reek, :roodi, :rcov]
|
20
|
-
config.graphs = [:flog, :flay, :reek, :roodi, :rcov]
|
21
|
-
config.flay = { :dirs_to_flay => ['lib'], :minimum_score => 100 }
|
22
|
-
config.flog = { :dirs_to_flog => [ 'lib'] }
|
23
|
-
config.reek = { :dirs_to_reek => ['lib'] }
|
24
|
-
config.roodi = { :dirs_to_roodi => ['lib'] }
|
25
|
-
config.saikuro = { :output_directory => 'scratch_directory/saikuro',
|
26
|
-
:input_directory => ['lib'],
|
27
|
-
:cyclo => "",
|
28
|
-
:filter_cyclo => "0",
|
29
|
-
:warn_cyclo => "5",
|
30
|
-
:error_cyclo => "7",
|
31
|
-
:formater => "text"} #this needs to be set to "text"
|
32
|
-
config.churn = { :start_date => "1 year ago", :minimum_churn_count => 10}
|
33
|
-
config.rcov = { :environment => 'test',
|
34
|
-
:test_files => ['spec/**/*_spec.rb'],
|
35
|
-
:rcov_opts => ["--sort coverage",
|
36
|
-
"--no-html",
|
37
|
-
"--text-coverage",
|
38
|
-
"--no-color",
|
39
|
-
"--profile",
|
40
|
-
"--rails",
|
41
|
-
"--exclude /gems/,/Library/,spec"]}
|
42
|
-
config.graph_engine = :bluff
|
43
|
-
end
|
44
|
-
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/numbers_in_words.rb
CHANGED
@@ -1,7 +1,24 @@
|
|
1
|
-
require '
|
1
|
+
require 'active_support/core_ext/array'
|
2
2
|
|
3
|
-
|
3
|
+
require "numbers_in_words/version"
|
4
|
+
require 'numbers_in_words/language_writer'
|
4
5
|
|
5
|
-
require '
|
6
|
-
require '
|
6
|
+
require 'numbers_in_words/english/constants'
|
7
|
+
require 'numbers_in_words/english/language_writer_english'
|
8
|
+
|
9
|
+
require 'numbers_in_words/number_group'
|
10
|
+
require 'numbers_in_words/number_parser'
|
11
|
+
require 'numbers_in_words/to_number'
|
12
|
+
require 'numbers_in_words/to_word'
|
13
|
+
|
14
|
+
module NumbersInWords
|
15
|
+
LENGTH_OF_GOOGOL = 101 #length of the string i.e. one with 100 zeros
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_writer :language
|
19
|
+
def language
|
20
|
+
@language ||= "English"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
7
24
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module NumbersInWords
|
2
|
+
def in_words language=NumbersInWords.language
|
3
|
+
NumbersInWords::ToWord.new(self, language).in_words
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module WordsInNumbers
|
8
|
+
def in_numbers language=NumbersInWords.language
|
9
|
+
NumbersInWords::ToNumber.new(self, language).in_numbers
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class String
|
14
|
+
include WordsInNumbers
|
15
|
+
end
|
16
|
+
|
17
|
+
class Numeric
|
18
|
+
include NumbersInWords
|
19
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module NumbersInWords
|
2
|
+
module English
|
3
|
+
def self.exceptions
|
4
|
+
{
|
5
|
+
0 => "zero",
|
6
|
+
1 => "one",
|
7
|
+
2 => "two",
|
8
|
+
3 => "three",
|
9
|
+
4 => "four",
|
10
|
+
5 => "five",
|
11
|
+
6 => "six",
|
12
|
+
7 => "seven",
|
13
|
+
8 => "eight",
|
14
|
+
9 => "nine",
|
15
|
+
|
16
|
+
10 => "ten",
|
17
|
+
11 => "eleven",
|
18
|
+
12 => "twelve",
|
19
|
+
|
20
|
+
13 => "thirteen",
|
21
|
+
14 => "fourteen",
|
22
|
+
15 => "fifteen",
|
23
|
+
16 => "sixteen" ,
|
24
|
+
17 => "seventeen",
|
25
|
+
18 => "eighteen",
|
26
|
+
19 => "nineteen",
|
27
|
+
|
28
|
+
20 => "twenty",
|
29
|
+
30 => "thirty",
|
30
|
+
40 => "forty",
|
31
|
+
50 => "fifty",
|
32
|
+
60 => "sixty",
|
33
|
+
70 => "seventy",
|
34
|
+
80 => "eighty",
|
35
|
+
90 => "ninety"
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.swap_keys hsh
|
40
|
+
hsh.inject({}){|h,(k,v)| h[v]=k; h}
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.powers_of_ten
|
44
|
+
{
|
45
|
+
0 => "one",
|
46
|
+
1 => "ten",
|
47
|
+
2 => "hundred",
|
48
|
+
3 => "thousand",
|
49
|
+
6 => "million",
|
50
|
+
9 => "billion",
|
51
|
+
12 => "trillion",
|
52
|
+
15 => "quadrillion",
|
53
|
+
18 => "quintillion",
|
54
|
+
21 => "sextillion",
|
55
|
+
24 => "septillion",
|
56
|
+
27 => "octillion",
|
57
|
+
30 => "nonillion",
|
58
|
+
33 => "decillion",
|
59
|
+
36 => "undecillion",
|
60
|
+
39 => "duodecillion",
|
61
|
+
42 => "tredecillion",
|
62
|
+
45 => "quattuordecillion",
|
63
|
+
48 => "quindecillion",
|
64
|
+
51 => "sexdecillion",
|
65
|
+
54 => "septendecillion",
|
66
|
+
57 => "octodecillion",
|
67
|
+
60 => "novemdecillion",
|
68
|
+
63 => "vigintillion",
|
69
|
+
66 => "unvigintillion",
|
70
|
+
69 => "duovigintillion",
|
71
|
+
72 => "trevigintillion",
|
72
|
+
75 => "quattuorvigintillion",
|
73
|
+
78 => "quinvigintillion",
|
74
|
+
81 => "sexvigintillion",
|
75
|
+
84 => "septenvigintillion",
|
76
|
+
87 => "octovigintillion",
|
77
|
+
90 => "novemvigintillion",
|
78
|
+
93 => "trigintillion",
|
79
|
+
96 => "untrigintillion",
|
80
|
+
99 => "duotrigintillion",
|
81
|
+
100 => "googol"
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.exceptions_to_i
|
86
|
+
swap_keys exceptions
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.powers_of_ten_to_i
|
90
|
+
swap_keys powers_of_ten
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module NumbersInWords
|
2
|
+
module English
|
3
|
+
class LanguageWriterEnglish < LanguageWriter
|
4
|
+
delegate :to_i, to: :that
|
5
|
+
|
6
|
+
def initialize that
|
7
|
+
super that
|
8
|
+
@language = "English"
|
9
|
+
end
|
10
|
+
|
11
|
+
def negative
|
12
|
+
"minus " + (-@that).in_words
|
13
|
+
end
|
14
|
+
|
15
|
+
def in_words
|
16
|
+
v = handle_exception
|
17
|
+
return v if v
|
18
|
+
|
19
|
+
in_decimals = decimals
|
20
|
+
return in_decimals if in_decimals
|
21
|
+
|
22
|
+
number = to_i
|
23
|
+
|
24
|
+
return negative() if number < 0
|
25
|
+
|
26
|
+
length = number.to_s.length
|
27
|
+
output = ""
|
28
|
+
|
29
|
+
if length == 2 #20-99
|
30
|
+
tens = (number/10).round*10 #write the tens
|
31
|
+
|
32
|
+
output << exceptions[tens] # e.g. eighty
|
33
|
+
|
34
|
+
digit = number - tens #write the digits
|
35
|
+
|
36
|
+
output << " " + digit.in_words unless digit==0
|
37
|
+
else
|
38
|
+
output << write() #longer numbers
|
39
|
+
end
|
40
|
+
|
41
|
+
output.strip
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def handle_exception
|
46
|
+
exceptions[@that] if @that.is_a?(Integer) and exceptions[@that]
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def write
|
51
|
+
length = @that.to_s.length
|
52
|
+
output =
|
53
|
+
if length == 3
|
54
|
+
#e.g. 113 splits into "one hundred" and "thirteen"
|
55
|
+
write_groups(2)
|
56
|
+
|
57
|
+
#more than one hundred less than one googol
|
58
|
+
elsif length < LENGTH_OF_GOOGOL
|
59
|
+
write_groups(3)
|
60
|
+
|
61
|
+
elsif length >= LENGTH_OF_GOOGOL
|
62
|
+
write_googols
|
63
|
+
end
|
64
|
+
output.strip
|
65
|
+
end
|
66
|
+
|
67
|
+
def decimals
|
68
|
+
int, decimals = NumberGroup.new(@that).split_decimals
|
69
|
+
if int
|
70
|
+
out = int.in_words + " point "
|
71
|
+
decimals.each do |decimal|
|
72
|
+
out << decimal.to_i.in_words + " "
|
73
|
+
end
|
74
|
+
out.strip
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def write_googols
|
80
|
+
googols, remainder = NumberGroup.new(@that).split_googols
|
81
|
+
output = ""
|
82
|
+
output << " " + googols.in_words + " googol"
|
83
|
+
if remainder > 0
|
84
|
+
prefix = " "
|
85
|
+
prefix << "and " if remainder < 100
|
86
|
+
output << prefix + remainder.in_words
|
87
|
+
end
|
88
|
+
output
|
89
|
+
end
|
90
|
+
|
91
|
+
def write_groups group
|
92
|
+
#e.g. 113 splits into "one hundred" and "thirteen"
|
93
|
+
output = ""
|
94
|
+
group_words(group) do |power, name, digits|
|
95
|
+
if digits > 0
|
96
|
+
prefix = " "
|
97
|
+
#no and between thousands and hundreds
|
98
|
+
prefix << "and " if power == 0 and digits < 100
|
99
|
+
output << prefix + digits.in_words
|
100
|
+
output << prefix + name unless power == 0
|
101
|
+
end
|
102
|
+
end
|
103
|
+
output
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|