├── .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 | {Build Status}[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 | --------------------------------------------------------------------------------