numbers_in_words 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8ae79a47b168a7aa57ce40a4cea7cfc6c35eae27
4
- data.tar.gz: dab916e39bd24eeaff4c02c06816874506b053cb
3
+ metadata.gz: f2857f0873d094b51ce7ca5e4a90ac8e999bebb6
4
+ data.tar.gz: b0e885874e36c119b69290ae0bb30110419f7031
5
5
  SHA512:
6
- metadata.gz: c6ae5c2eb9cad7c67563c2cad679741098841c7d74b12ac0dfa45a50082baedb511c3bb58b1a92f887bdb5857d9189c048addecdaa387a47cbe76f58fab26816
7
- data.tar.gz: 76d00e6c848827df1739a9d0b0d3476b061c5355a5d2fb13eab0d2a432c1d9c061a133b76d17286690d0408879e9857085d86ce86c31243038f2776f7e2db5d1
6
+ metadata.gz: 98b44efc9e3c5bd28d5f3f26d11fd4b62f12fde010ce4ca30fe35e57ca10a8cb0822ac50fcb977a4823d7c722a75c71c670b52584c5361c2aae8fe2527ff95a9
7
+ data.tar.gz: 99b23e8c07dea648e1afc393f28f4b8981594c30a05bb4970b616cd76098294ddaf7b450bf683dd1656e8cadf01573b7fcf86164e32d12afbba95adde26e31ed
data/.codeclimate.yml CHANGED
@@ -10,3 +10,5 @@ ratings:
10
10
  - "**.rb"
11
11
  exclude_paths:
12
12
  - spec/**/*
13
+ - .codeclimate.yml
14
+ - .rubocop.yml
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  [![Build Status](http://img.shields.io/travis/markburns/numbers_in_words.svg)](https://travis-ci.org/markburns/numbers_in_words)
2
2
  [![Dependency Status](http://img.shields.io/gemnasium/markburns/numbers_in_words.svg)](https://gemnasium.com/markburns/numbers_in_words)
3
3
  [![Code Climate](http://img.shields.io/codeclimate/github/markburns/numbers_in_words.svg)](https://codeclimate.com/github/markburns/numbers_in_words)
4
+ [![Test Coverage](https://codeclimate.com/github/markburns/numbers_in_words/badges/coverage.svg)](https://codeclimate.com/github/markburns/numbers_in_words/coverage)
4
5
  [![Gem Version](http://img.shields.io/gem/v/numbers_in_words.svg)](https://rubygems.org/gems/numbers_in_words)
5
6
  [![License](http://img.shields.io/:license-mit-blue.svg)](http://markburns.mit-license.org)
6
- [![Badges](http://img.shields.io/:badges-6/6-ff6799.svg)](https://github.com/badges/badgerbadgerbadger)
7
+ [![Badges](http://img.shields.io/:badges-7/7-ff6799.svg)](https://github.com/badges/badgerbadgerbadger)
7
8
 
8
9
  Installation
9
10
  ============
@@ -12,7 +13,7 @@ Installation
12
13
  gem 'numbers_in_words'
13
14
 
14
15
  require 'numbers_in_words'
15
- require 'numbers_in_words/duck_punch' #see why later
16
+ require 'numbers_in_words/duck_punch' #optional see why later
16
17
  ```
17
18
 
18
19
  This project was created for a test for a job interview. I haven't really used
@@ -27,7 +28,7 @@ require 'numbers_in_words'
27
28
  NumbersInWords.in_words(112)
28
29
  #=> one hundred and twelve
29
30
 
30
- NumbersInWords.in_numbers(112)
31
+ NumbersInWords.in_numbers("one googol")
31
32
  #=>10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
32
33
 
33
34
  NumbersInWords.in_numbers("Seventy million, five-hundred and fifty six thousand point eight nine three")
data/bin/spec CHANGED
File without changes
@@ -21,12 +21,12 @@ module NumbersInWords
21
21
  @language ||= "English"
22
22
  end
23
23
 
24
- def in_words(i, language=NumbersInWords.language)
25
- NumbersInWords::ToWord.new(i, language).in_words
24
+ def in_words(i, language=NumbersInWords.language, only_compress=false)
25
+ NumbersInWords::ToWord.new(i, language).in_words(only_compress)
26
26
  end
27
27
 
28
- def in_numbers(s, language=NumbersInWords.language)
29
- NumbersInWords::ToNumber.new(s, language).in_numbers
28
+ def in_numbers(s, language=NumbersInWords.language, only_compress=false)
29
+ NumbersInWords::ToNumber.new(s, language).in_numbers(only_compress)
30
30
  end
31
31
  end
32
32
  end
@@ -5,8 +5,8 @@ module NumbersInWords
5
5
  end
6
6
 
7
7
  module WordsInNumbers
8
- def in_numbers language=NumbersInWords.language
9
- NumbersInWords.in_numbers(self, language)
8
+ def in_numbers(only_compress = false, language=NumbersInWords.language)
9
+ NumbersInWords::ToNumber.new(self, language).in_numbers(only_compress)
10
10
  end
11
11
  end
12
12
 
@@ -1,5 +1,14 @@
1
1
  module NumbersInWords
2
2
  module English
3
+
4
+ def self.canonize(w)
5
+ aliases = {
6
+ "oh" => "zero"
7
+ }
8
+ canon = aliases[w]
9
+ return canon ? canon : w
10
+ end
11
+
3
12
  def self.exceptions
4
13
  {
5
14
  0 => "zero",
@@ -89,5 +98,27 @@ module NumbersInWords
89
98
  def self.powers_of_ten_to_i
90
99
  swap_keys powers_of_ten
91
100
  end
101
+
102
+ POWERS_RX = Regexp.union(powers_of_ten.values[1..-1])
103
+
104
+ def self.check_mixed(txt)
105
+ mixed = txt.match /^(-?\d+(.\d+)?) (#{POWERS_RX}s?)$/
106
+ if mixed && mixed[1] && mixed[3]
107
+ matches = [mixed[1], mixed[3]].map{ |m| NumbersInWords.in_numbers m }
108
+ return matches.reduce(&:*)
109
+ end
110
+ end
111
+
112
+ def self.check_one(txt)
113
+ one = txt.match /^one (#{POWERS_RX})$/
114
+ end
115
+
116
+ def self.strip_minus(txt)
117
+ stripped = txt.gsub(/^minus/, "") if txt =~ /^minus/
118
+ end
119
+
120
+ def self.check_decimal(txt)
121
+ txt.match(/\spoint\s/)
122
+ end
92
123
  end
93
124
  end
@@ -23,22 +23,29 @@ module NumbersInWords
23
23
 
24
24
  return negative() if number < 0
25
25
 
26
- length = number.to_s.length
26
+ output = if number.to_s.length == 2 #20-99
27
+ handle_tens(number)
28
+ else
29
+ write() #longer numbers
30
+ end
31
+
32
+ output.strip
33
+ end
34
+
35
+ def handle_tens(number)
27
36
  output = ""
28
37
 
29
- if length == 2 #20-99
30
- tens = (number/10).round*10 #write the tens
38
+ tens = (number/10).round*10 #write the tens
31
39
 
32
- output << exceptions[tens] # e.g. eighty
40
+ output << exceptions[tens] # e.g. eighty
33
41
 
34
- digit = number - tens #write the digits
42
+ digit = number - tens #write the digits
35
43
 
36
- output << " " + NumbersInWords.in_words(digit) unless digit==0
37
- else
38
- output << write() #longer numbers
44
+ unless digit == 0
45
+ output << " " + NumbersInWords.in_words(digit)
39
46
  end
40
47
 
41
- output.strip
48
+ output
42
49
  end
43
50
 
44
51
  def handle_exception
@@ -15,7 +15,6 @@ module NumbersInWords
15
15
  end
16
16
  end
17
17
 
18
-
19
18
  def group_words size
20
19
  #1000 and over Numbers are split into groups of three
21
20
  groups = NumberGroup.groups_of @that, size
@@ -3,8 +3,8 @@ module NumbersInWords
3
3
  include Enumerable
4
4
  attr_accessor :number
5
5
 
6
- def each
7
- @array.each { |item| yield item}
6
+ def self.groups_of number, size
7
+ new(number).groups(size)
8
8
  end
9
9
 
10
10
  def initialize number
@@ -42,10 +42,6 @@ module NumbersInWords
42
42
  end
43
43
  end
44
44
 
45
- def self.groups_of number, size
46
- new(number).groups(size)
47
- end
48
-
49
45
  def split_googols
50
46
  googols = @number.to_s[0 .. (-LENGTH_OF_GOOGOL)].to_i
51
47
  remainder = @number.to_s[(1-LENGTH_OF_GOOGOL) .. -1].to_i
@@ -1,4 +1,5 @@
1
1
  module NumbersInWords::NumberParser
2
+ SCALES_N = [100, 1000, 1000000, 1000000000, 1000000000000, 10**100]
2
3
  # Example: 364,895,457,898
3
4
  #three hundred and sixty four billion eight hundred and ninety five million
4
5
  #four hundred and fifty seven thousand eight hundred and ninety eight
@@ -39,7 +40,7 @@ module NumbersInWords::NumberParser
39
40
  #3. add memory to answer,reset, because power of ten>2 0 2000
40
41
  #4. add 1 to memory 1 2000
41
42
  #5. finish - add memory to answer 0 2001
42
- def parse(integers)
43
+ def parse_ints(integers)
43
44
  memory = 0
44
45
  answer = 0
45
46
  reset = true #reset each time memory is reset
@@ -58,8 +59,7 @@ module NumbersInWords::NumberParser
58
59
  reset = true
59
60
  end
60
61
  end
61
-
62
- if memory < integer
62
+ if memory < integer
63
63
  memory *= integer
64
64
  else
65
65
  memory += integer
@@ -69,6 +69,18 @@ module NumbersInWords::NumberParser
69
69
  answer += memory
70
70
  end
71
71
 
72
+ def parse(integers, only_compress = false)
73
+ if integers.length < 2
74
+ return integers if only_compress
75
+ return integers.empty? ? 0 : integers[0]
76
+ end
77
+ if [] == (SCALES_N & integers)
78
+ return pair_parse(integers, only_compress)
79
+ end
80
+
81
+ parse_ints(integers)
82
+ end
83
+
72
84
  def power_of_ten integer
73
85
  Math.log10(integer)
74
86
  end
@@ -77,5 +89,47 @@ module NumbersInWords::NumberParser
77
89
  power_of_ten(integer) == power_of_ten(integer).to_i
78
90
  end
79
91
 
92
+ # 15,16
93
+ # 85,16
94
+ def pair_parse(ints, only_compress = false)
95
+ ints = compress(ints)
96
+ return ints if only_compress
97
+ return ints[0] if ints.length == 1
98
+ sum = 0
99
+ ints.each do |n|
100
+ sum *= n >= 10 ? 100 : 10
101
+ sum += n
102
+ end
103
+ sum
104
+ end
105
+
106
+ # [40, 2] => [42]
107
+ def compress(ints)
108
+ res = []
109
+ i = 0
110
+ return [] if ints.empty?
111
+ while i < ints.length - 1
112
+ int, jump = compress_int(ints[i], ints[i + 1])
113
+ res << int
114
+ i += jump
115
+ end
116
+ if i < ints.length
117
+ res << ints[-1]
118
+ else
119
+ res
120
+ end
121
+ end
122
+
123
+ def compress_int(int, sequel)
124
+ tens = int % 10 == 0 && int > 10
125
+ if tens && sequel < 10
126
+ return [int + sequel, 2]
127
+ else
128
+ return [int, 1]
129
+ end
130
+
131
+ [res, jump]
132
+ end
133
+
80
134
  extend self
81
135
  end
@@ -1,6 +1,7 @@
1
1
  class NumbersInWords::ToNumber
2
2
  delegate :to_s, to: :that
3
- delegate :powers_of_ten_to_i, :exceptions_to_i, to: :language
3
+ delegate :powers_of_ten_to_i, :exceptions_to_i, :canonize, \
4
+ :check_mixed, :check_one, :strip_minus, :check_decimal, to: :language
4
5
  attr_reader :that, :language
5
6
 
6
7
  def initialize that, language=NumbersInWords.language
@@ -16,27 +17,42 @@ class NumbersInWords::ToNumber
16
17
  end
17
18
  end
18
19
 
19
- def handle_negative text
20
- -1 * (text.gsub(/^minus /, "")).in_numbers if text =~ /^minus /
20
+ def handle_negative(text, only_compress)
21
+ stripped = strip_minus text
22
+ if stripped
23
+ stripped_n = NumbersInWords.in_numbers(stripped, language, only_compress)
24
+ only_compress ? stripped_n.map{ |k| k * -1 } : -1 * stripped_n
25
+ end
21
26
  end
22
27
 
23
- def in_numbers
24
- text = to_s
28
+ def in_numbers(only_compress = false)
29
+ text = to_s.strip
30
+ return text.to_f if text =~ /^-?\d+(.\d+)?$/
25
31
 
26
32
  text = strip_punctuation text
27
- i = handle_negative text
33
+
34
+ i = handle_negative(text, only_compress)
28
35
  return i if i
29
36
 
37
+ mixed = check_mixed text
38
+ return mixed if mixed
39
+
40
+ one = check_one text
41
+ if one
42
+ res = NumbersInWords.in_numbers(one[1], language)
43
+ return only_compress ? [res] : res
44
+ end
45
+
30
46
  h = handle_decimals text
31
47
  return h if h
32
48
 
33
49
  integers = word_array_to_integers text.split(" ")
34
50
 
35
- NumbersInWords::NumberParser.parse integers
51
+ NumbersInWords::NumberParser.parse integers, only_compress
36
52
  end
37
53
 
38
54
  def strip_punctuation text
39
- text = text.downcase.gsub(/[^a-z ]/, " ")
55
+ text = text.downcase.gsub(/[^a-z 0-9]/, " ")
40
56
  to_remove = true
41
57
 
42
58
  to_remove = text.gsub! " ", " " while to_remove
@@ -45,28 +61,18 @@ class NumbersInWords::ToNumber
45
61
  end
46
62
 
47
63
  def handle_decimals text
48
- match = text.match(/\spoint\s/)
64
+ match = check_decimal text
49
65
  if match
50
- integer = match.pre_match.in_numbers
51
-
52
- decimal = decimal_portion match.post_match
53
-
54
- integer + decimal
66
+ integer = NumbersInWords.in_numbers(match.pre_match)
67
+ decimal = NumbersInWords.in_numbers(match.post_match)
68
+ integer += "0.#{decimal}".to_f
55
69
  end
56
70
  end
57
71
 
58
-
59
- def decimal_portion text
60
- words = text.split " "
61
- integers = word_array_to_integers words
62
- decimal = "0." + integers.join()
63
- decimal.to_f
64
- end
65
-
66
72
  #handles simple single word numbers
67
73
  #e.g. one, seven, twenty, eight, thousand etc
68
74
  def word_to_integer word
69
- text = word.to_s.chomp.strip
75
+ text = canonize(word.to_s.chomp.strip)
70
76
 
71
77
  exception = exceptions_to_i[text]
72
78
  return exception if exception
@@ -1,3 +1,3 @@
1
1
  module NumbersInWords
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -9,8 +9,8 @@ Gem::Specification.new do |gem|
9
9
  gem.summary = "Example: 123.in_words # => \"one hundred and twenty three\", \"seventy-five point eight\".in_numbers # = > 75.8"
10
10
 
11
11
  gem.version = NumbersInWords::VERSION
12
- gem.authors = ["Mark Burns"]
13
- gem.email = ["markthedeveloper@gmail.com"]
12
+ gem.authors = ["Mark Burns", "Dimid Duchovny"]
13
+ gem.email = ["markthedeveloper@gmail.com", "dimidd@gmail.com"]
14
14
  gem.homepage = "http://github.com/markburns/numbers_in_words"
15
15
 
16
16
  gem.add_dependency "activesupport"
@@ -19,6 +19,12 @@ describe "NumbersInWords" do
19
19
  describe ".in_numbers" do
20
20
  it do
21
21
  expect(NumbersInWords.in_numbers("one hundred")).to eq 100
22
+
23
+ expect(NumbersInWords.in_numbers("minus one hundred")).to eq -100
24
+ expect(NumbersInWords.in_numbers("twenty four" )).to eq 24
25
+ expect(NumbersInWords.in_numbers("one point two")).to eq 1.2
26
+ expect(NumbersInWords.in_numbers("one hundred googol")).to eq 100*10**100
27
+ expect(NumbersInWords.in_numbers("one hundred googol and thirty")).to eq 30 + 100*10**100
22
28
  end
23
29
  end
24
30
 
@@ -0,0 +1,19 @@
1
+ require './spec/spec_helper'
2
+
3
+ describe NumbersInWords do
4
+ it "should recognize numerical strings" do
5
+ arr = %w(8 56 100 5789 3435356)
6
+ arr.each{ |s| expect(s.in_numbers).to eql(s.to_f) }
7
+ end
8
+
9
+ it "should recognize mixed strings" do
10
+ mixed = {
11
+ "19 hundred" => 1_900.0,
12
+ "20 thousand" => 20_000.0,
13
+ "100 million" => 100_000_000.0,
14
+ "7 billion" => 7_000_000_000.0,
15
+ "42 trillion" => 42_000_000_000_000.0
16
+ }
17
+ mixed.each{ |k, v| expect(k.in_numbers).to eql(v) }
18
+ end
19
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,7 @@
1
+ require "codeclimate-test-reporter"
2
+ CodeClimate::TestReporter.start
3
+
1
4
  require 'numbers_in_words'
2
5
  require 'numbers_in_words/duck_punch'
3
6
 
4
- require "codeclimate-test-reporter"
5
- CodeClimate::TestReporter.start
6
7
 
@@ -132,4 +132,14 @@ describe WordsInNumbers do
132
132
  in_numbers).to eq(75.84327694518)
133
133
  end
134
134
 
135
+ it "should handle years notation" do
136
+ expect("fifteen sixteen".in_numbers) .to eq(1516)
137
+ expect("eighty five sixteen".in_numbers) .to eq(8516)
138
+ expect("nineteen ninety six".in_numbers) .to eq(1996)
139
+ expect("forty nine ninety eight forty seven seventy nine".in_numbers) .to eq(49984779)
140
+ expect("fifteen sixteen".in_numbers) .to eq(1516)
141
+ expect("fifteen sixteen seven".in_numbers) .to eq(15167)
142
+ expect("fifteen sixteen seventeen".in_numbers) .to eq(151617)
143
+ end
144
+
135
145
  end
@@ -0,0 +1,17 @@
1
+ require './spec/spec_helper'
2
+
3
+ describe WordsInNumbers do
4
+ it "should handle years notation" do
5
+ expect("fifteen sixteen seventeen".in_numbers).to eq(151617)
6
+ expect("forty nine ninety eight forty seven seventy nine".in_numbers).to eq(49984779)
7
+ expect("sixty seven six".in_numbers) .to eq(676)
8
+ expect("one fifty".in_numbers).to eq(150)
9
+ expect("two fifty".in_numbers).to eq(250)
10
+ expect("one point fifty six fifty seven".in_numbers).to eq(1.5657)
11
+ expect("one three forty seven".in_numbers).to eq(1347)
12
+ expect("one three five point forty seven".in_numbers).to eq(135.47)
13
+ expect("one ten sixty three".in_numbers).to eq(11063)
14
+ expect("one nineteen ten oh five".in_numbers).to eq(1191005)
15
+ end
16
+
17
+ end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: numbers_in_words
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Burns
8
+ - Dimid Duchovny
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2015-11-30 00:00:00.000000000 Z
12
+ date: 2015-12-02 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: activesupport
@@ -41,6 +42,7 @@ dependencies:
41
42
  description: "#in_words method for integers and #in_numbers for strings"
42
43
  email:
43
44
  - markthedeveloper@gmail.com
45
+ - dimidd@gmail.com
44
46
  executables:
45
47
  - spec
46
48
  extensions: []
@@ -71,8 +73,10 @@ files:
71
73
  - spec/non_monkey_patch_spec.rb
72
74
  - spec/number_group_spec.rb
73
75
  - spec/numbers_in_words_spec.rb
76
+ - spec/numerical_strings_spec.rb
74
77
  - spec/spec_helper.rb
75
78
  - spec/words_in_numbers_spec.rb
79
+ - spec/years_spec.rb
76
80
  homepage: http://github.com/markburns/numbers_in_words
77
81
  licenses: []
78
82
  metadata: {}
@@ -102,5 +106,7 @@ test_files:
102
106
  - spec/non_monkey_patch_spec.rb
103
107
  - spec/number_group_spec.rb
104
108
  - spec/numbers_in_words_spec.rb
109
+ - spec/numerical_strings_spec.rb
105
110
  - spec/spec_helper.rb
106
111
  - spec/words_in_numbers_spec.rb
112
+ - spec/years_spec.rb