During my 30 days of code challenge I’m working now on fun project which convert amount of money from user input to amount of subways, which you may build in Omsk. For example: some politician had bought the watches for 12 billion roubles. Cost of Omsk Subway project is about 24 billion roubles. So, instead of buying such expensive stuff our subject could build half of Omsk Subway.
It is to easy to restrict user input by integers only. And I decided to accept whatever user wants. For example it may be “3 billions 542 millions” or “3.42 billions”. And I need to convert this string to float, and that’s interesting task.
For this project I’m using Ruby on Rails, full repository available at github.
Failing tests for service object
It’s easy: we need to throw different strings into our object called StringDecoder.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| require 'rails_helper'
RSpec.describe StringDecoder do
let(:decoder) { StringDecoder.new }
it 'decodes numeric string' do
input = '1231412353'
expect(decoder.call(input)).to equal(1231412353.0)
end
it 'decodes numeric string with spaces' do
input = '1 412 935 315'
expect(decoder.call(input)).to equal(1412935315.0)
end
it 'decodes numeric string with punctuation' do
input = '1,426,233,234.43'
expect(decoder.call(input)).to equal(1426233234.43)
end
it 'decodes string with миллиард миллион тысяча' do
input = '1 billion 149 millions 51 thousand'
expect(decoder.call(input)).to equal(1149051000.0)
end
it 'decodes string with punctuation and words' do
input = '1.44 billions'
expect(decoder.call(input)).to equal(1440000000.0)
end
end
|
Writing StringDecoder service object
Next part is more interesting. We need to parse user input, remove unnecessary symbols, convert words to numbers, etc.
I thought this part will be hard, but I was wrong. When I started coding, everything I need just appeared in my head. Just read this code, it has comments (sic!).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| # Convert human entered string into number
class StringDecoder
# Hash with powers of 10 by names
POWERS = {
'trillion' => 10**12,
'trillions' => 10**12,
'billion' => 10**9,
'billions' => 10**9,
'million' => 10**6,
'millions' => 10**6,
'thousand' => 10**3,
'thousands' => 10**3,
}.freeze
# Only one public method for service object: #call
def call(input)
# Return if input is numeric (and don't forget to strip spaces and commas)
return input.gsub(/[\s,]/, '').to_f if float? input.gsub(/[\s,]/, '')
result = 0
# First line: insert spacebar if there is no one between integer and word
# Second line: split array by space
# Third line: convert each word to its numeric format
# Fourth line: slice array by 2 elements
# Fifth line: multiply each chunk and sum everything
input.gsub(/(?<=[\d])(?=[а-яА-Я])/, ' ')
.split(' ')
.map { |i| POWERS[i] ? POWERS[i].to_f : i.to_f }
.each_slice(2) do |slice|
result += slice.inject(:*)
end
result
end
private
# Check if given string may be converted to float
def float?(input)
input[/\A[\d]+[\.]?[\d]*\z/] == input
end
end
|
Of course I have to do more:
- I need to convert words like one, two, three, etc to number
- I need to log wrong user inputs to upgrade this service object
- I need to return something if converting failed
- …
- Profit
But for now it’s working.
So Long, and Thanks for all the Fish!