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 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