numbers_in_words 0.1.1 → 0.2.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.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.swp
2
+ *.swo
3
+ *.html
4
+ tmp
5
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in numbers_in_words.gemspec
4
+ gemspec
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 'rubygems'
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"
@@ -1,7 +1,24 @@
1
- require 'rubygems'
1
+ require 'active_support/core_ext/array'
2
2
 
3
- $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__)))
3
+ require "numbers_in_words/version"
4
+ require 'numbers_in_words/language_writer'
4
5
 
5
- require 'numbers'
6
- require 'words'
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