├── .document
├── .github
└── workflows
│ └── test.yaml
├── .gitignore
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.rdoc
├── Rakefile
├── lib
├── numerizer.rb
├── numerizer
│ └── version.rb
├── provider.rb
└── providers
│ └── english_provider.rb
├── numerizer.gemspec
└── test
├── test_helper.rb
└── test_numerizer_en.rb
/.document:
--------------------------------------------------------------------------------
1 | README.rdoc
2 | lib/**/*.rb
3 | bin/*
4 | features/**/*.feature
5 | LICENSE
6 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 |
2 | name: test
3 |
4 | on: [ push, pull_request ]
5 |
6 | jobs:
7 |
8 | test:
9 |
10 | if: " ! (contains(github.event.head_commit.message, 'skip ci') || contains(github.event.head_commit.message, 'ci skip'))"
11 |
12 | name: ${{matrix.ruby}} on ${{matrix.os}}
13 |
14 | strategy:
15 | matrix:
16 | os: [ ubuntu-latest ]
17 | ruby: [ 2.3, 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, jruby-9.2, jruby-9.3, jruby-9.4, truffleruby-22 ]
18 | experimental: [ false ]
19 | fail-fast: false
20 | runs-on: ${{matrix.os}}
21 | continue-on-error: ${{matrix.experimental}}
22 |
23 | steps:
24 | - uses: actions/checkout@v3
25 | - uses: ruby/setup-ruby@v1
26 | with:
27 | ruby-version: ${{matrix.ruby}}
28 | bundler-cache: true
29 | - run: bundle exec rake
30 |
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sw?
2 | .DS_Store
3 | coverage
4 | rdoc
5 | pkg
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gemspec
4 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | numerizer (0.2.0)
5 |
6 | GEM
7 | remote: https://rubygems.org/
8 | specs:
9 | minitest (5.15.0)
10 | rake (13.0.6)
11 |
12 | PLATFORMS
13 | ruby
14 |
15 | DEPENDENCIES
16 | minitest (~> 5.0)
17 | numerizer!
18 | rake (~> 13)
19 |
20 | BUNDLED WITH
21 | 2.3.6
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 Tom Preston-Werner
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = Numerizer
2 | {
}[https://github.com/jduff/numerizer/actions/workflows/test.yaml?query=branch%3Amaster]
3 |
4 | Numerizer is a gem to help with parsing numbers in natural language from strings (ex forty two). It was extracted from the awesome Chronic gem https://github.com/mojombo/chronic.
5 |
6 | == Installation
7 |
8 | $ gem install numerizer
9 |
10 | == Usage
11 |
12 | >> require 'numerizer'
13 | => true
14 | >> Numerizer.numerize('forty two')
15 | => "42"
16 | >> Numerizer.numerize('two and a half')
17 | => "2.5"
18 | >> Numerizer.numerize('three quarters')
19 | => "3/4"
20 | >> Numerizer.numerize('two and three eighths')
21 | => "2.375"
22 |
23 | == Contributors
24 | Tom Preston-Werner, John Duff
25 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'rake'
3 |
4 | $:.unshift File.expand_path('../lib', __FILE__)
5 | require 'numerizer/version'
6 |
7 | def version
8 | Numerizer::VERSION
9 | end
10 |
11 | require 'rake/testtask'
12 | Rake::TestTask.new(:test) do |t|
13 | t.libs << 'test'
14 | t.test_files = Dir['test/test_*.rb']
15 | end
16 |
17 | begin
18 | require 'rcov/rcovtask'
19 | Rcov::RcovTask.new do |test|
20 | test.libs << 'test'
21 | test.pattern = 'test/**/test_*.rb'
22 | test.verbose = true
23 | end
24 | rescue LoadError
25 | task :rcov do
26 | abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
27 | end
28 | end
29 |
30 | desc "Release Numerizer version #{version}"
31 | task :release => :build do
32 | unless `git branch` =~ /^\* master$/
33 | puts "You must be on the master branch to release!"
34 | exit!
35 | end
36 | sh "git commit --allow-empty -a -m 'Release #{version}'"
37 | sh "git tag v#{version}"
38 | sh "git push origin master"
39 | sh "git push origin v#{version}"
40 | sh "gem push pkg/numerizer-#{version}.gem"
41 | end
42 |
43 | desc 'Build a gem from the gemspec'
44 | task :build do
45 | FileUtils.mkdir_p 'pkg'
46 | sh 'gem build numerizer.gemspec'
47 | FileUtils.mv("./numerizer-#{version}.gem", "pkg")
48 | end
49 |
50 |
51 | task :default => :test
52 |
--------------------------------------------------------------------------------
/lib/numerizer.rb:
--------------------------------------------------------------------------------
1 | # LICENSE:
2 | #
3 | # (The MIT License)
4 | #
5 | # Copyright © 2008 Tom Preston-Werner
6 | #
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 | #
9 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | #
11 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12 |
13 | require 'numerizer/version'
14 | require 'providers/english_provider'
15 | require 'set'
16 |
17 | class Numerizer
18 |
19 | PROVIDERS = {'en' => EnglishProvider.new}
20 |
21 | def self.numerize(string,lang: 'en', ignore: [], bias: :none)
22 | string = string.dup
23 | ignore = ignore.map(&:downcase).to_set
24 | provider = PROVIDERS[lang]
25 | if provider == nil
26 | raise "Language #{lang} not found. Language options include #{PROVIDERS.keys}"
27 | end
28 | provider.numerize(string, ignore: ignore, bias: bias)
29 | end
30 |
31 | end
32 |
--------------------------------------------------------------------------------
/lib/numerizer/version.rb:
--------------------------------------------------------------------------------
1 | class Numerizer
2 | VERSION = '0.2.0'
3 | end
4 |
--------------------------------------------------------------------------------
/lib/provider.rb:
--------------------------------------------------------------------------------
1 | class GenericProvider
2 |
3 | def numerize(str, ignore: [], bias: :none)
4 | preprocess(str, ignore)
5 | numerize_numerals(str, ignore, bias)
6 | numerize_fractions(str, ignore, bias)
7 | numerize_ordinals(str, ignore, bias)
8 | numerize_big_prefixes(str, ignore, bias)
9 | postprocess(str, ignore)
10 | end
11 |
12 | private
13 |
14 | def preprocess(str, ignore)
15 | raise 'must be implemented in subclass'
16 | end
17 | def numerize_numerals(str, ignore, bias)
18 | raise 'must be implemented in subclass'
19 | end
20 | def numerize_fractions(str, ignore, bias)
21 | raise 'must be implemented in subclass'
22 | end
23 | def numerize_ordinals(str, ignore, bias)
24 | raise 'must be implemented in subclass'
25 | end
26 | def numerize_big_prefixes(str, ignore, bias)
27 | raise 'must be implemented in subclass'
28 | end
29 | def postprocess(str, ignore)
30 | raise 'must be implemented in subclass'
31 | end
32 |
33 | # Turns list of words into a unionized list, ignoring words specified in
34 | # arguments or that meet the conditions of the yield block
35 | def regexify(words, ignore:[])
36 | if block_given?
37 | return Regexp.union(words.reject { |x| ignore.include?(x) || yield(x) })
38 | else
39 | return Regexp.union(words.reject { |x| ignore.include?(x) })
40 | end
41 | end
42 |
43 | end
44 |
--------------------------------------------------------------------------------
/lib/providers/english_provider.rb:
--------------------------------------------------------------------------------
1 | require 'provider'
2 | require 'strscan'
3 |
4 | class EnglishProvider < GenericProvider
5 |
6 | DIRECT_NUMS = {
7 | 'eleven' => '11',
8 | 'twelve' => '12',
9 | 'thirteen' => '13',
10 | 'fourteen' => '14',
11 | 'fifteen' => '15',
12 | 'sixteen' => '16',
13 | 'seventeen' => '17',
14 | 'eighteen' => '18',
15 | 'nineteen' => '19',
16 | 'ninteen' => '19',
17 | 'zero' => '0',
18 | 'ten' => '10',
19 | }
20 |
21 | SINGLE_NUMS = {
22 | 'one' => 1,
23 | 'two' => 2,
24 | 'three' => 3,
25 | 'four' => 4,
26 | 'five' => 5,
27 | 'six' => 6,
28 | 'seven' => 7,
29 | 'eight' => 8,
30 | 'nine' => 9
31 | }
32 |
33 | TEN_PREFIXES = {
34 | 'twenty' => 20,
35 | 'thirty' => 30,
36 | 'forty' => 40,
37 | 'fourty' => 40,
38 | 'fifty' => 50,
39 | 'sixty' => 60,
40 | 'seventy' => 70,
41 | 'eighty' => 80,
42 | 'ninety' => 90
43 | }
44 |
45 | BIG_PREFIXES = {
46 | 'hundred' => 100,
47 | 'thousand' => 1000,
48 | 'million' => 1_000_000,
49 | 'billion' => 1_000_000_000,
50 | 'trillion' => 1_000_000_000_000,
51 | }
52 |
53 | FRACTIONS = {
54 | 'half' => 2,
55 | 'halves' => 2,
56 | 'quarter' => 4,
57 | 'quarters' => 4
58 | }
59 |
60 | ORDINALS = {
61 | 'first' => 1,
62 | 'second' => 2,
63 | }
64 |
65 | SINGLE_ORDINAL_FRACTIONALS = {
66 | 'third' => 3,
67 | 'fourth' => 4,
68 | 'fifth' => 5,
69 | 'sixth' => 6,
70 | 'seventh' => 7,
71 | 'eighth' => 8,
72 | 'ninth' => 9,
73 | }
74 |
75 | DIRECT_ORDINAL_FRACTIONALS = {
76 | 'tenth' => '10',
77 | 'eleventh' => '11',
78 | 'twelfth' => '12',
79 | 'thirteenth' => '13',
80 | 'fourteenth' => '14',
81 | 'fifteenth' => '15',
82 | 'sixteenth' => '16',
83 | 'seventeenth' => '17',
84 | 'eighteenth' => '18',
85 | 'nineteenth' => '19',
86 | 'twentieth' => '20',
87 | 'thirtieth' => '30',
88 | 'fourtieth' => '40',
89 | 'fiftieth' => '50',
90 | 'sixtieth' => '60',
91 | 'seventieth' => '70',
92 | 'eightieth' => '80',
93 | 'ninetieth' => '90'
94 | }
95 |
96 | ALL_ORDINALS = ORDINALS.merge(SINGLE_ORDINAL_FRACTIONALS).merge(DIRECT_ORDINAL_FRACTIONALS)
97 | ONLY_PLURAL_FRACTIONS = FRACTIONS.merge((SINGLE_ORDINAL_FRACTIONALS.merge(DIRECT_ORDINAL_FRACTIONALS)).inject({ }) {|h, (k,v)| h[k + 's'] = v ; h})
98 | ALL_FRACTIONS = ONLY_PLURAL_FRACTIONS.merge(SINGLE_ORDINAL_FRACTIONALS).merge(DIRECT_ORDINAL_FRACTIONALS)
99 |
100 | DIRECT_SINGLE_NUMS = DIRECT_NUMS.merge(SINGLE_NUMS)
101 | DIRECT_NUMS_TEN_PREFIXES = DIRECT_NUMS.merge(TEN_PREFIXES)
102 | ORDINAL_SINGLE = ORDINALS.merge(SINGLE_ORDINAL_FRACTIONALS)
103 |
104 | # REGEXP.UNION here breaks insertion into negative Lookbehind
105 | ALL_ORDINALS_REGEX = ALL_ORDINALS.keys.reduce {|a,b| a + '|' + b}
106 | PRONOUNS = ['i','you','he','she','we','it','you','they','to','the'].reduce {|a,b| a + '|' + b}
107 |
108 | def preprocess(string, ignore)
109 | string.gsub!(/ +|([^\d])-([^\d])/, '\1 \2') # will mutilate hyphenated-words
110 | string.gsub!(/\ba$/, '') && string.rstrip! # doesn't make sense for an 'a' at the end to be a 1
111 | end
112 |
113 | def numerize_numerals(string, ignore, bias)
114 | single_nums = regexify(SINGLE_NUMS.keys, ignore: ignore)
115 | dir_single_nums = regexify(DIRECT_SINGLE_NUMS.keys, ignore: ignore)
116 | ten_prefs = regexify(TEN_PREFIXES.keys, ignore: ignore)
117 | dir_nums_ten_prefs = regexify(DIRECT_NUMS_TEN_PREFIXES.keys, ignore: ignore)
118 | single_ords = regexify(ORDINAL_SINGLE.keys, ignore: ignore)
119 |
120 | # easy/direct replacements
121 | string.gsub!(/(^|\W)(#{single_nums})\s(#{dir_nums_ten_prefs})(?=$|\W)/i) {$1 << $2 << ' hundred ' << $3}
122 | string.gsub!(/(^|\W)(#{dir_single_nums})(?=$|\W)/i) { $1 << '' << DIRECT_SINGLE_NUMS[$2].to_s}
123 | if bias == :ordinal
124 | string.gsub!(/(^|\W)\ba\b(?=$|\W)(?! (?:#{ALL_ORDINALS_REGEX}))/i, '\1' + 1.to_s)
125 | else
126 | string.gsub!(/(^|\W)\ba\b(?=$|\W)/i, '\1' + 1.to_s)
127 | end
128 |
129 | # ten, twenty, etc.
130 | string.gsub!(/(^|\W)(#{ten_prefs})(#{single_nums})(?=$|\W)/i) { $1 << '' << (TEN_PREFIXES[$2] + SINGLE_NUMS[$3]).to_s}
131 | string.gsub!(/(^|\W)(#{ten_prefs})(\s)?(#{single_ords})(?=$|\W)/i) { $1 << '' << (TEN_PREFIXES[$2] + ORDINAL_SINGLE[$4]).to_s << $4[-2, 2]}
132 | string.gsub!(/(^|\W)(#{ten_prefs})(?=$|\W)/i) { $1 << '' << TEN_PREFIXES[$2].to_s}
133 | end
134 |
135 | def numerize_fractions(string, ignore, bias)
136 | # handle fractions
137 | # only plural fractions if ordinal mode
138 | # Ignore quarter to be handled seperately if not fractional mode
139 | if bias == :ordinal
140 | fractionals = regexify(ONLY_PLURAL_FRACTIONS.keys, ignore: ignore + ['quarter', 'quarters'])
141 | elsif bias == :fractional
142 | fractionals = regexify(ALL_FRACTIONS.keys, ignore: ignore)
143 | else
144 | fractionals = regexify(ALL_FRACTIONS.keys, ignore: ignore + ['quarter', 'quarters'])
145 | end
146 | quarters = regexify(['quarter', 'quarters'], ignore: ignore)
147 |
148 | string.gsub!(/a (#{fractionals})(?=$|\W)/i) {'1/' << ALL_FRACTIONS[$1].to_s}
149 | # TODO : Find Noun Distinction for Quarter
150 | if bias == :fractional
151 | string.gsub!(/(^|\W)(#{fractionals})(?=$|\W)/i) {'/' << ALL_FRACTIONS[$2].to_s}
152 | else
153 | string.gsub!(/(?' << ALL_ORDINALS['second'].to_s << 'second'[-2, 2] }
165 | end
166 | string.gsub!(/(^|\W)(#{all_ords})(?=$|\W)/i) { $1 << '' << ALL_ORDINALS[$2].to_s << $2[-2, 2]}
167 | end
168 |
169 | # hundreds, thousands, millions, etc.
170 | def numerize_big_prefixes(string, ignore, bias)
171 | # big_prefs = regexify(BIG_PREFIXES.keys, ignore: ignore)
172 | BIG_PREFIXES.each do |k,v|
173 | next if ignore.include? k.downcase
174 | string.gsub!(/(?:)?(\d*) *#{k}/i) { $1.empty? ? v : '' << (v * $1.to_i).to_s }
175 | andition(string)
176 | end
177 | end
178 |
179 | def postprocess(string, ignore)
180 | andition(string)
181 | numerize_halves(string, ignore)
182 | #Strip Away Added Num Tags
183 | string.gsub(//, '')
184 | end
185 |
186 | private
187 |
188 | def cleanup_fractions(string)
189 | # evaluate fractions when preceded by another number
190 | string.gsub!(/(\d+)(?: | and |-)+(|\s)*(\d+)\s*\/\s*(\d+)/i) { ($1.to_f + ($3.to_f/$4.to_f)).to_s }
191 | # fix unpreceeded fractions
192 | string.gsub!(/(?:^|\W)\/(\d+)/, '1/\1')
193 | string.gsub!(/(?<=[a-zA-Z])\/(\d+)/, ' 1/\1')
194 | end
195 |
196 | # always substitute halfs
197 | def numerize_halves(string, ignore)
198 | return if ignore.include? 'half'
199 | string.gsub!(/\bhalf\b/i, '1/2')
200 | end
201 |
202 | def andition(string)
203 | sc = StringScanner.new(string)
204 | while(sc.scan_until(/(\d+)( | and )(\d+)(?=[^\w]|$)/i))
205 | if sc[2] =~ /and/ || sc[1].size > sc[3].size
206 | string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '' << (sc[1].to_i + sc[3].to_i).to_s
207 | sc.reset
208 | end
209 | end
210 | end
211 |
212 | end
213 |
--------------------------------------------------------------------------------
/numerizer.gemspec:
--------------------------------------------------------------------------------
1 | $:.unshift File.expand_path('../lib', __FILE__)
2 | require 'numerizer/version'
3 |
4 | Gem::Specification.new do |s|
5 | s.name = "numerizer"
6 | s.version = Numerizer::VERSION
7 |
8 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9 | s.require_paths = ["lib"]
10 | s.authors = ["John Duff"]
11 | s.date = "2014-04-23"
12 | s.description = "Numerizer is a gem to help with parsing numbers in natural language from strings (ex forty two). It was extracted from the awesome Chronic gem http://github.com/evaryont/chronic."
13 | s.email = "duff.john@gmail.com"
14 | s.extra_rdoc_files = [
15 | "LICENSE",
16 | "README.rdoc"
17 | ]
18 | s.files = `git ls-files`.split($/)
19 | s.test_files = `git ls-files -- test`.split($/)
20 | s.homepage = "http://github.com/jduff/numerizer"
21 | s.licenses = ["MIT"]
22 | s.rubygems_version = "2.2.2"
23 | s.summary = "Numerizer is a gem to help with parsing numbers in natural language from strings (ex forty two)."
24 |
25 | s.add_development_dependency 'rake', '~> 13'
26 | s.add_development_dependency 'minitest', '~> 5.0'
27 | end
28 |
29 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2 | $LOAD_PATH.unshift(File.dirname(__FILE__))
3 | require 'numerizer'
4 |
5 | require 'minitest/autorun'
6 |
7 | class TestCase < Minitest::Test
8 | end
9 |
--------------------------------------------------------------------------------
/test/test_numerizer_en.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), 'test_helper')
2 |
3 | class NumerizerTestEN < TestCase
4 | def test_en_argument
5 | assert_equal '12', Numerizer.numerize('twelve', lang: 'en')
6 | assert_raises RuntimeError do
7 | Numerizer.numerize('twelve', lang: 'english')
8 | end
9 | end
10 |
11 | def test_straight_parsing
12 | strings = {
13 | 1 => 'one',
14 | 5 => 'five',
15 | 10 => 'ten',
16 | 11 => 'eleven',
17 | 12 => 'twelve',
18 | 13 => 'thirteen',
19 | 14 => 'fourteen',
20 | 15 => 'fifteen',
21 | 16 => 'sixteen',
22 | 17 => 'seventeen',
23 | 18 => 'eighteen',
24 | 19 => 'nineteen',
25 | 20 => 'twenty',
26 | 27 => 'twenty seven',
27 | 31 => 'thirty-one',
28 | 37 => 'thirty-seven',
29 | 41 => 'forty one',
30 | 42 => 'fourty two',
31 | 59 => 'fifty nine',
32 | 100 => ['one hundred', 'a hundred', 'hundred a'],
33 | 150 => ['one hundred and fifty', 'one fifty'],
34 | 219 => ['two hundred and nineteen', 'two hundred nineteen', 'two nineteen'],
35 | 200 => 'two-hundred',
36 | 500 => '5 hundred',
37 | 999 => 'nine hundred and ninety nine',
38 | 1_000 => 'one thousand',
39 | 1_200 => ['twelve hundred', 'one thousand two hundred'],
40 | 17_000 => 'seventeen thousand',
41 | 21_473 => 'twentyone-thousand-four-hundred-and-seventy-three',
42 | 74_002 => 'seventy four thousand and two',
43 | 99_999 => 'ninety nine thousand nine hundred ninety nine',
44 | 100_000 => '100 thousand',
45 | 250_000 => 'two hundred fifty thousand',
46 | 1_000_000 => 'one million',
47 | 1_250_007 => 'one million two hundred fifty thousand and seven',
48 | 1_000_000_000 => 'one billion',
49 | 1_000_000_001 => 'one billion and one'
50 | }
51 |
52 | strings.sort.each do |key, value|
53 | Array(value).each do |value|
54 | assert_equal key, Numerizer.numerize(value).to_i
55 | end
56 | end
57 |
58 | assert_equal "1/2", Numerizer.numerize("half")
59 | assert_equal "1/4", Numerizer.numerize("quarter")
60 | end
61 |
62 | def test_combined_double_digets
63 | assert_equal "21", Numerizer.numerize("twentyone")
64 | assert_equal "37", Numerizer.numerize("thirtyseven")
65 | end
66 |
67 | def test_fractions_in_words
68 | assert_equal "1/2", Numerizer.numerize("one half")
69 |
70 | assert_equal "1/4", Numerizer.numerize("1 quarter")
71 | assert_equal "1/4", Numerizer.numerize("one quarter")
72 | assert_equal "1/4", Numerizer.numerize("a quarter")
73 | assert_equal "1/8", Numerizer.numerize("one eighth")
74 |
75 | assert_equal "3/4", Numerizer.numerize("three quarters")
76 | assert_equal "2/4", Numerizer.numerize("two fourths")
77 | assert_equal "3/8", Numerizer.numerize("three eighths")
78 | assert_equal "7/10", Numerizer.numerize("seven tenths")
79 | end
80 |
81 | def test_fractional_addition
82 | assert_equal "1.25", Numerizer.numerize("one and a quarter")
83 | assert_equal "2.375", Numerizer.numerize("two and three eighths")
84 | assert_equal "2.5", Numerizer.numerize("two and a half")
85 | assert_equal "3.5 hours", Numerizer.numerize("three and a half hours")
86 | end
87 |
88 | def test_word_with_a_number
89 | assert_equal "pennyweight", Numerizer.numerize("pennyweight")
90 | end
91 |
92 | def test_edges
93 | assert_equal "27 Oct 2006 7:30am", Numerizer.numerize("27 Oct 2006 7:30am")
94 | end
95 |
96 | def test_multiple_slashes_should_not_be_evaluated
97 | assert_equal '11/02/2007', Numerizer.numerize('11/02/2007')
98 | end
99 |
100 | def test_compatability
101 | assert_equal '1/2', Numerizer.numerize('1/2')
102 | assert_equal '05/06', Numerizer.numerize('05/06')
103 | assert_equal "3.5 hours", Numerizer.numerize("three and a half hours")
104 | assert_equal "1/2 an hour", Numerizer.numerize("half an hour")
105 | end
106 |
107 | def test_ordinal_strings
108 | {
109 | 'first' => '1st',
110 | 'second' => '2nd',
111 | 'third' => '3rd',
112 | 'fourth' => '4th',
113 | 'fifth' => '5th',
114 | 'seventh' => '7th',
115 | 'eighth' => '8th',
116 | 'tenth' => '10th',
117 | 'eleventh' => '11th',
118 | 'twelfth' => '12th',
119 | 'thirteenth' => '13th',
120 | 'sixteenth' => '16th',
121 | 'twentieth' => '20th',
122 | 'twenty-third' => '23rd',
123 | 'thirtieth' => '30th',
124 | 'thirty-first' => '31st',
125 | 'fourtieth' => '40th',
126 | 'fourty ninth' => '49th',
127 | 'fiftieth' => '50th',
128 | 'sixtieth' => '60th',
129 | 'seventieth' => '70th',
130 | 'eightieth' => '80th',
131 | 'ninetieth' => '90th',
132 | 'hundredth' => '100th',
133 | 'thousandth' => '1000th',
134 | 'millionth' => '1000000th',
135 | 'billionth' => '1000000000th',
136 | 'trillionth' => '1000000000000th',
137 | 'first day month two' => '1st day month 2'
138 | }.each do |key, val|
139 | assert_equal val, Numerizer.numerize(key)
140 | end
141 | end
142 |
143 | def test_ambiguous_cases
144 | # Quarter ( Coin ) is Untested
145 | # Second ( Time / Verb ) is Untested
146 | assert_equal 'the 4th', Numerizer.numerize('the fourth')
147 | assert_equal '1/3 of', Numerizer.numerize('a third of')
148 | assert_equal '4th', Numerizer.numerize('fourth')
149 | assert_equal '2nd', Numerizer.numerize('second')
150 | assert_equal 'I quarter', Numerizer.numerize('I quarter')
151 | assert_equal 'You quarter', Numerizer.numerize('You quarter')
152 | assert_equal 'I want to quarter', Numerizer.numerize('I want to quarter')
153 | assert_equal 'the 1st 1/4', Numerizer.numerize('the first quarter')
154 | assert_equal '1/4 pound of beef', Numerizer.numerize('quarter pound of beef')
155 | assert_equal 'the 2nd second', Numerizer.numerize('the second second')
156 | assert_equal 'the 4th second', Numerizer.numerize('the fourth second')
157 | assert_equal '1 second', Numerizer.numerize('one second')
158 |
159 | # TODO: Find way to distinguish this verb
160 | # assert_equal 'I peel and quarter bananas', Numerizer.numerize('I peel and quarter bananas')
161 | end
162 |
163 | def test_ignore
164 | assert_equal 'the second day of march', Numerizer.numerize('the second day of march', ignore: ['second'])
165 | assert_equal 'quarter', Numerizer.numerize('quarter', ignore: ['quarter'])
166 | assert_equal 'the five guys', Numerizer.numerize('the five guys', ignore: ['five'])
167 | assert_equal 'the fifty 2', Numerizer.numerize('the fifty two', ignore: ['fifty'])
168 | end
169 |
170 | def test_bias_ordinal
171 | assert_equal '4th', Numerizer.numerize('fourth', bias: :ordinal)
172 | assert_equal '12th', Numerizer.numerize('twelfth', bias: :ordinal)
173 | assert_equal '2nd', Numerizer.numerize('second', bias: :ordinal)
174 | assert_equal 'the 4th', Numerizer.numerize('the fourth', bias: :ordinal)
175 | assert_equal '2.75', Numerizer.numerize('two and three fourths', bias: :ordinal)
176 | assert_equal '3/5', Numerizer.numerize('three fifths', bias: :ordinal)
177 | assert_equal 'a 4th of', Numerizer.numerize('a fourth of', bias: :ordinal)
178 | assert_equal 'I quarter your home', Numerizer.numerize('I quarter your home', bias: :ordinal)
179 | assert_equal 'the 1st 2nd 3rd', Numerizer.numerize('the first second third', bias: :ordinal)
180 | end
181 |
182 | def test_bias_fractional
183 | assert_equal '1/4', Numerizer.numerize('fourth', bias: :fractional)
184 | assert_equal '1/12', Numerizer.numerize('twelfth', bias: :fractional)
185 | assert_equal '2nd', Numerizer.numerize('second', bias: :fractional)
186 | assert_equal 'the 1/4', Numerizer.numerize('the fourth', bias: :fractional)
187 | assert_equal '2.75', Numerizer.numerize('two and three fourths', bias: :fractional)
188 | assert_equal '3/5', Numerizer.numerize('three fifths', bias: :fractional)
189 | assert_equal '1/4 of', Numerizer.numerize('a fourth of', bias: :fractional)
190 | assert_equal 'I 1/4 your home', Numerizer.numerize('I quarter your home', bias: :fractional)
191 | assert_equal 'the 1st second 1/3', Numerizer.numerize('the first second third', bias: :fractional)
192 | end
193 | end
194 |
--------------------------------------------------------------------------------