string_to_number 0.1.3 → 0.2.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
- SHA1:
3
- metadata.gz: 344ba3e9aaef1e44c05dd7e76f4ba19f0f10ad1c
4
- data.tar.gz: 531faee1be5f9feb4935389f74aa47f84ca5fa44
2
+ SHA256:
3
+ metadata.gz: 94ccd123e31951522c7960a5140affb629f331e8f862e3f0336c3c15c2c86bcc
4
+ data.tar.gz: b2d95e4cd38fda267ed0f73399c5af5b5e511d9f9f2baba3e14f349e6da4bf7b
5
5
  SHA512:
6
- metadata.gz: df3acb7157af2369badd36fb71a956159a90f7b711ba6b04afdc7c23b71124429d27ee9a8510d99abf4d129c2288119f45459044b6bda7669c77d0e544d5b977
7
- data.tar.gz: 702f06ab46ff773541df886c4a1ad074572bbbc5c02c38b22b95622d130ae4d39d01a8eb6bac8444853ed885c65a0c9a3625e25c0988dd7ea95560d553721af0
6
+ metadata.gz: 604ed5a2557ceb9813fee18765e0aacc33bdc6fd7a721f5d97568c65995a8e9d5609ff17eb2a44412b381a8ac10822ee602633ad9b5a0e67a984b4de0f33aba4
7
+ data.tar.gz: bdff30862931f8d0d28d0c15ccf36b70056fe65c3b588c619c9452c7f92a4a732569ac4925e68af88fe67682f49854aa809b7468ee7d81005b44fd33b1c537ba
data/.gitignore CHANGED
@@ -1,6 +1,5 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
- /Gemfile.lock
4
3
  /_yardoc/
5
4
  /coverage/
6
5
  /doc/
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 2.7.8
data/CLAUDE.md ADDED
@@ -0,0 +1,103 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ This is a Ruby gem that converts French words into numbers. The gem provides a single public method `StringToNumber.in_numbers(french_string)` that parses French number words and returns their numeric equivalent.
8
+
9
+ ## Core Architecture
10
+
11
+ - **Main module**: `StringToNumber` in `lib/string_to_number.rb` - provides the public API
12
+ - **Optimized parser**: `StringToNumber::Parser` in `lib/string_to_number/parser.rb` - high-performance implementation
13
+ - **Original implementation**: `StringToNumber::ToNumber` in `lib/string_to_number/to_number.rb` - legacy compatibility
14
+ - **Version**: `StringToNumber::VERSION` in `lib/string_to_number/version.rb`
15
+
16
+ ### Parser Architecture
17
+
18
+ The optimized parser uses:
19
+ - **WORD_VALUES**: Direct French word to number mappings (0-90, including regional variants)
20
+ - **MULTIPLIERS**: Power-of-ten multipliers (cent=2, mille=3, million=6, etc.)
21
+ - **Pre-compiled regex patterns**: Eliminate compilation overhead
22
+ - **Multi-level caching**: Instance cache + LRU conversion cache
23
+ - **Thread-safe design**: Concurrent access with mutex protection
24
+
25
+ The algorithm maintains the proven recursive parsing logic from the original while adding:
26
+ - Memoization for repeated conversions
27
+ - Instance caching to reduce initialization costs
28
+ - Optimized string operations and hash lookups
29
+
30
+ ## Common Development Commands
31
+
32
+ ```bash
33
+ # Install dependencies
34
+ bundle install
35
+
36
+ # Run tests
37
+ rake spec
38
+
39
+ # Run specific test
40
+ bundle exec rspec spec/string_to_number_spec.rb
41
+
42
+ # Start interactive console
43
+ rake console
44
+ # or
45
+ bundle exec irb -I lib -r string_to_number
46
+
47
+ # Install gem locally
48
+ bundle exec rake install
49
+
50
+ # Release new version (updates version.rb, creates git tag, pushes to rubygems)
51
+ bundle exec rake release
52
+ ```
53
+
54
+ ## Testing
55
+
56
+ Uses RSpec with comprehensive test coverage for French number parsing from 0 to millions. Tests are organized by number ranges (0-9, 10-19, 20-29, etc.) and include complex multi-word numbers.
57
+
58
+ ### Performance Testing
59
+
60
+ Performance tests are available to measure and monitor the implementation's efficiency:
61
+
62
+ ```bash
63
+ # Run comprehensive performance test suite
64
+ bundle exec rspec spec/performance_spec.rb
65
+
66
+ # Run standalone benchmark script
67
+ ruby -I lib benchmark.rb
68
+
69
+ # Run micro-benchmarks to identify bottlenecks
70
+ ruby -I lib microbenchmark.rb
71
+
72
+ # Run profiling analysis
73
+ ruby -I lib profile.rb
74
+ ```
75
+
76
+ **Performance Characteristics (Optimized Implementation):**
77
+ - Simple numbers (0-100): ~0.001ms average, 800,000+ conversions/sec
78
+ - Medium complexity (100-1000): ~0.001ms average, 780,000+ conversions/sec
79
+ - Complex numbers (1000+): ~0.002ms average, 690,000+ conversions/sec
80
+ - Exceptional scalability: minimal performance degradation with input length
81
+ - Memory efficient: zero object creation during operation
82
+ - Intelligent caching: repeated conversions benefit from memoization
83
+
84
+ **Performance Improvements:**
85
+ - **14-460x faster** than original implementation across all test cases
86
+ - **Excellent scalability**: 1.3x degradation vs 43x in original
87
+ - **Pre-compiled regex patterns** eliminate compilation overhead
88
+ - **Instance caching** reduces initialization costs
89
+ - **Memoization** speeds up repeated conversions
90
+ - **Thread-safe** with concurrent performance >2M conversions/sec
91
+
92
+ **Usage Options:**
93
+ ```ruby
94
+ # Use optimized implementation (default)
95
+ StringToNumber.in_numbers('vingt et un')
96
+
97
+ # Use original implementation for compatibility
98
+ StringToNumber.in_numbers('vingt et un', use_optimized: false)
99
+
100
+ # Cache management
101
+ StringToNumber.clear_caches!
102
+ StringToNumber.cache_stats
103
+ ```
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ string_to_number (0.2.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.5.0)
10
+ rake (13.0.6)
11
+ rspec (3.11.0)
12
+ rspec-core (~> 3.11.0)
13
+ rspec-expectations (~> 3.11.0)
14
+ rspec-mocks (~> 3.11.0)
15
+ rspec-core (3.11.0)
16
+ rspec-support (~> 3.11.0)
17
+ rspec-expectations (3.11.1)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.11.0)
20
+ rspec-mocks (3.11.1)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.11.0)
23
+ rspec-support (3.11.1)
24
+
25
+ PLATFORMS
26
+ x86_64-linux
27
+
28
+ DEPENDENCIES
29
+ bundler
30
+ rake
31
+ rspec
32
+ string_to_number!
33
+
34
+ BUNDLED WITH
35
+ 2.3.26
data/README.md CHANGED
@@ -1,8 +1,32 @@
1
1
  # StringToNumber
2
2
 
3
- A ruby gem to convert French words into numbers.
3
+ [![Gem Version](https://badge.fury.io/rb/string_to_number.svg)](https://badge.fury.io/rb/string_to_number)
4
+ [![Ruby](https://github.com/FabienPiette/string_to_number/workflows/Ruby/badge.svg)](https://github.com/FabienPiette/string_to_number/actions)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
6
 
5
- ## Installation
7
+ A high-performance Ruby gem for converting French written numbers into their numeric equivalents. Features intelligent caching, thread-safe operations, and support for complex French number formats.
8
+
9
+ ## ✨ Features
10
+
11
+ - **High Performance**: Up to 460x faster than naive implementations with intelligent caching
12
+ - **Thread-Safe**: Concurrent access support with proper locking mechanisms
13
+ - **Comprehensive**: Handles complex French number formats including:
14
+ - Basic numbers (zéro, un, deux...)
15
+ - Compound numbers (vingt et un, quatre-vingt-quatorze...)
16
+ - Large numbers (millions, milliards, billions...)
17
+ - Special cases (quatre-vingts, soixante-dix...)
18
+ - **Memory Efficient**: LRU cache with configurable limits
19
+ - **Backward Compatible**: Maintains compatibility with original implementation
20
+
21
+ ## 🚀 Performance
22
+
23
+ | Input Size | Original | Optimized | Improvement |
24
+ |------------|----------|-----------|-------------|
25
+ | Short | 0.5ms | 0.035ms | **14x** |
26
+ | Medium | 2.1ms | 0.045ms | **47x** |
27
+ | Long | 23ms | 0.05ms | **460x** |
28
+
29
+ ## 📦 Installation
6
30
 
7
31
  Add this line to your application's Gemfile:
8
32
 
@@ -12,45 +36,176 @@ gem 'string_to_number'
12
36
 
13
37
  And then execute:
14
38
 
15
- $ bundle
39
+ ```bash
40
+ $ bundle install
41
+ ```
16
42
 
17
43
  Or install it yourself as:
18
44
 
19
- $ gem install string_to_number
45
+ ```bash
46
+ $ gem install string_to_number
47
+ ```
48
+
49
+ ## 🔧 Usage
20
50
 
21
- ## Usage
51
+ ### Basic Usage
22
52
 
23
53
  ```ruby
24
54
  require 'string_to_number'
25
55
 
26
- StringToNumber.in_numbers('zéro')
27
- #=> 0
56
+ # Simple numbers
57
+ StringToNumber.in_numbers('zéro') #=> 0
58
+ StringToNumber.in_numbers('quinze') #=> 15
59
+ StringToNumber.in_numbers('cent') #=> 100
60
+
61
+ # Compound numbers
62
+ StringToNumber.in_numbers('vingt et un') #=> 21
63
+ StringToNumber.in_numbers('quatre-vingt-quatorze') #=> 94
64
+ StringToNumber.in_numbers('soixante-dix') #=> 70
65
+
66
+ # Large numbers
67
+ StringToNumber.in_numbers('mille deux cent trente-quatre') #=> 1234
68
+ StringToNumber.in_numbers('un million') #=> 1_000_000
69
+ StringToNumber.in_numbers('trois milliards') #=> 3_000_000_000
70
+
71
+ # Complex expressions
72
+ StringToNumber.in_numbers('neuf mille neuf cent quatre-vingt-dix-neuf') #=> 9999
73
+ StringToNumber.in_numbers('deux millions trois cent mille') #=> 2_300_000
74
+ ```
75
+
76
+ ### Advanced Features
77
+
78
+ ```ruby
79
+ # Validation
80
+ StringToNumber.valid_french_number?('vingt et un') #=> true
81
+ StringToNumber.valid_french_number?('hello world') #=> false
82
+
83
+ # Cache management
84
+ StringToNumber.clear_caches! # Clear all internal caches
85
+ stats = StringToNumber.cache_stats
86
+ puts "Cache hit ratio: #{stats[:cache_hit_ratio]}"
87
+
88
+ # Backward compatibility mode
89
+ StringToNumber.in_numbers('cent', use_optimized: false) #=> 100
90
+ ```
91
+
92
+ ### Supported Number Formats
93
+
94
+ | Range | Examples |
95
+ |-------|----------|
96
+ | 0-19 | zéro, un, deux, trois, quatre, cinq, six, sept, huit, neuf, dix, onze, douze, treize, quatorze, quinze, seize, dix-sept, dix-huit, dix-neuf |
97
+ | 20-99 | vingt, trente, quarante, cinquante, soixante, soixante-dix, quatre-vingts, quatre-vingt-dix |
98
+ | 100+ | cent, mille, million, milliard, billion |
99
+ | Compounds | vingt et un, quatre-vingt-quatorze, deux mille trois |
100
+
101
+ ## ⚡ Performance Tips
102
+
103
+ 1. **Reuse conversions**: The gem automatically caches results for better performance
104
+ 2. **Batch processing**: Use the optimized parser (default) for better throughput
105
+ 3. **Memory management**: Call `clear_caches!` periodically if processing many unique inputs
106
+ 4. **Thread safety**: The gem is thread-safe and can be used in concurrent environments
107
+
108
+ ## 🧪 Development
109
+
110
+ After checking out the repo, run `bin/setup` to install dependencies:
28
111
 
29
- StringToNumber.in_numbers('dix-neuf')
30
- #=> 19
112
+ ```bash
113
+ $ git clone https://github.com/FabienPiette/string_to_number.git
114
+ $ cd string_to_number
115
+ $ bin/setup
116
+ ```
117
+
118
+ ### Running Tests
119
+
120
+ ```bash
121
+ # Run all tests
122
+ $ rake spec
123
+
124
+ # Run performance tests
125
+ $ ruby benchmark.rb
126
+
127
+ # Run specific test files
128
+ $ rspec spec/string_to_number_spec.rb
129
+ ```
31
130
 
32
- StringToNumber.in_numbers('quatre-vingt-quatorze')
33
- #=> 94
131
+ ### Performance Benchmarking
34
132
 
35
- StringToNumber.in_numbers('neuf mille neuf cent quatre-vingt-dix neuf')
36
- #=> 9999
133
+ ```bash
134
+ # Compare implementations
135
+ $ ruby performance_comparison.rb
37
136
 
38
- StringToNumber.in_numbers('un million')
39
- #=> 1000000
137
+ # Detailed micro-benchmarks
138
+ $ ruby microbenchmark.rb
40
139
  ```
41
140
 
42
- ## Development
141
+ ### Interactive Console
142
+
143
+ ```bash
144
+ $ bin/console
145
+ # => Interactive prompt for experimentation
146
+ ```
147
+
148
+ ## 🏗️ Architecture
149
+
150
+ The gem uses a dual-architecture approach:
151
+
152
+ - **Optimized Parser** (`StringToNumber::Parser`): High-performance implementation with caching
153
+ - **Original Implementation** (`StringToNumber::ToNumber`): Reference implementation for compatibility
154
+
155
+ Key performance optimizations:
156
+ - Pre-compiled regex patterns
157
+ - LRU caching with thread-safe access
158
+ - Memoized parser instances
159
+ - Zero-allocation number matching
160
+
161
+ ## 🤝 Contributing
162
+
163
+ Bug reports and pull requests are welcome on GitHub at https://github.com/FabienPiette/string_to_number.
164
+
165
+ ### Development Process
166
+
167
+ 1. Fork the repository
168
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
169
+ 3. Write tests for your changes
170
+ 4. Ensure all tests pass (`rake spec`)
171
+ 5. Run performance tests to avoid regressions
172
+ 6. Commit your changes (`git commit -am 'Add amazing feature'`)
173
+ 7. Push to the branch (`git push origin feature/amazing-feature`)
174
+ 8. Open a Pull Request
175
+
176
+ ### Code of Conduct
177
+
178
+ This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
179
+
180
+ ## 📋 Requirements
181
+
182
+ - Ruby 2.5 or higher
183
+ - No external dependencies (uses only Ruby standard library)
184
+
185
+ ## 🐛 Troubleshooting
186
+
187
+ ### Common Issues
188
+
189
+ **Q: Numbers aren't parsing correctly**
190
+ A: Ensure your input uses proper French number words. Use `valid_french_number?` to validate input.
191
+
192
+ **Q: Performance seems slow**
193
+ A: Make sure you're using the default optimized parser. Check cache statistics with `cache_stats`.
43
194
 
44
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
195
+ **Q: Memory usage is high**
196
+ A: Call `clear_caches!` periodically if processing many unique number strings.
45
197
 
46
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
198
+ ## 📝 Changelog
47
199
 
48
- ## Contributing
200
+ See [CHANGELOG.md](CHANGELOG.md) for version history and updates.
49
201
 
50
- Bug reports and pull requests are welcome on GitHub at https://github.com/FabienPiette/string_to_number. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
202
+ ## 📄 License
51
203
 
204
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
52
205
 
53
- ## License
206
+ ## 🙏 Acknowledgments
54
207
 
55
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
208
+ - Original implementation by [Fabien Piette](https://github.com/FabienPiette)
209
+ - Performance optimizations and enhancements
210
+ - Community contributors and testers
56
211
 
data/benchmark.rb ADDED
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Performance benchmark script for StringToNumber gem
5
+ # Run with: ruby benchmark.rb
6
+
7
+ require_relative 'lib/string_to_number'
8
+ require 'benchmark'
9
+
10
+ class StringToNumberBenchmark
11
+ # Test data organized by complexity
12
+ TEST_CASES = {
13
+ simple: [
14
+ 'un', 'vingt', 'cent', 'mille'
15
+ ],
16
+ medium: [
17
+ 'vingt et un', 'deux cent cinquante', 'mille deux cent'
18
+ ],
19
+ complex: [
20
+ 'trois milliards cinq cents millions',
21
+ 'soixante-quinze million trois cent quarante six mille sept cent quatre-vingt-dix neuf'
22
+ ],
23
+ edge_cases: [
24
+ 'VINGT', 'une', 'septante', 'quatre-vingts'
25
+ ]
26
+ }.freeze
27
+
28
+ def self.run_benchmark
29
+ puts "StringToNumber Performance Benchmark"
30
+ puts "=" * 50
31
+ puts "Ruby version: #{RUBY_VERSION}"
32
+ puts "Platform: #{RUBY_PLATFORM}"
33
+ puts
34
+
35
+ # Warm up
36
+ puts "Warming up..."
37
+ TEST_CASES.values.flatten.each { |text| StringToNumber.in_numbers(text) }
38
+ puts
39
+
40
+ total_results = {}
41
+
42
+ TEST_CASES.each do |category, test_cases|
43
+ puts "#{category.to_s.capitalize} Numbers:"
44
+ puts "-" * 30
45
+
46
+ results = benchmark_category(test_cases)
47
+ total_results[category] = results
48
+
49
+ puts "Cases: #{test_cases.size}"
50
+ puts "Total time: #{results[:total_time].round(4)}s"
51
+ puts "Average per conversion: #{results[:avg_time_ms].round(4)}ms"
52
+ puts "Conversions per second: #{results[:ops_per_sec].round(0)}"
53
+ puts
54
+
55
+ # Show individual case performance for complex numbers
56
+ if category == :complex
57
+ puts "Individual case breakdown:"
58
+ test_cases.each_with_index do |text, index|
59
+ individual_time = Benchmark.realtime do
60
+ 1000.times { StringToNumber.in_numbers(text) }
61
+ end
62
+ avg_ms = (individual_time / 1000) * 1000
63
+ puts " #{index + 1}. #{avg_ms.round(4)}ms - '#{text[0..50]}#{text.length > 50 ? '...' : ''}'"
64
+ end
65
+ puts
66
+ end
67
+ end
68
+
69
+ # Summary
70
+ puts "=" * 50
71
+ puts "PERFORMANCE SUMMARY"
72
+ puts "=" * 50
73
+
74
+ total_results.each do |category, results|
75
+ status = case results[:avg_time_ms]
76
+ when 0..0.1 then "🟢 Excellent"
77
+ when 0.1..0.5 then "🟡 Good"
78
+ when 0.5..1.0 then "🟠 Acceptable"
79
+ else "🔴 Needs optimization"
80
+ end
81
+
82
+ puts "#{category.to_s.capitalize.ljust(12)} #{status.ljust(15)} #{results[:avg_time_ms].round(4)}ms avg"
83
+ end
84
+
85
+ puts
86
+ puts "Memory efficiency test..."
87
+ test_memory_usage
88
+
89
+ puts
90
+ puts "Scalability test..."
91
+ test_scalability
92
+ end
93
+
94
+ private
95
+
96
+ def self.benchmark_category(test_cases, iterations = 2000)
97
+ total_time = Benchmark.realtime do
98
+ test_cases.each do |text|
99
+ iterations.times do
100
+ StringToNumber.in_numbers(text)
101
+ end
102
+ end
103
+ end
104
+
105
+ total_conversions = test_cases.size * iterations
106
+ avg_time_ms = (total_time / total_conversions) * 1000
107
+ ops_per_sec = total_conversions / total_time
108
+
109
+ {
110
+ total_time: total_time,
111
+ avg_time_ms: avg_time_ms,
112
+ ops_per_sec: ops_per_sec
113
+ }
114
+ end
115
+
116
+ def self.test_memory_usage
117
+ # Test memory efficiency
118
+ if Object.const_defined?(:ObjectSpace)
119
+ GC.start
120
+ initial_objects = ObjectSpace.count_objects[:TOTAL]
121
+
122
+ # Perform intensive operations
123
+ 500.times do
124
+ TEST_CASES.values.flatten.each { |text| StringToNumber.in_numbers(text) }
125
+ end
126
+
127
+ GC.start
128
+ final_objects = ObjectSpace.count_objects[:TOTAL]
129
+ object_growth = final_objects - initial_objects
130
+
131
+ puts "Object creation: #{object_growth} new objects (#{object_growth > 1000 ? '🔴 High' : '🟢 Low'})"
132
+ else
133
+ puts "Memory tracking not available on this platform"
134
+ end
135
+ end
136
+
137
+ def self.test_scalability
138
+ # Test how performance scales with input complexity
139
+ inputs = [
140
+ 'un', # 2 chars
141
+ 'vingt et un', # 11 chars
142
+ 'mille deux cent trente-quatre', # 29 chars
143
+ 'trois milliards cinq cents millions deux cent mille et une' # 58 chars
144
+ ]
145
+
146
+ puts "Input length vs. performance:"
147
+
148
+ results = inputs.map do |input|
149
+ time = Benchmark.realtime do
150
+ 1000.times { StringToNumber.in_numbers(input) }
151
+ end
152
+ avg_ms = (time / 1000) * 1000
153
+
154
+ { length: input.length, time: avg_ms, input: input }
155
+ end
156
+
157
+ results.each do |result|
158
+ complexity_ratio = result[:time] / results.first[:time]
159
+ status = complexity_ratio < 5 ? "🟢" : complexity_ratio < 10 ? "🟡" : "🔴"
160
+
161
+ puts " #{result[:length].to_s.rjust(2)} chars: #{result[:time].round(4)}ms #{status} (#{complexity_ratio.round(1)}x baseline)"
162
+ end
163
+
164
+ # Check if performance degrades reasonably
165
+ worst_ratio = results.last[:time] / results.first[:time]
166
+ if worst_ratio < 10
167
+ puts "✅ Scalability: Good (#{worst_ratio.round(1)}x degradation)"
168
+ else
169
+ puts "❌ Scalability: Poor (#{worst_ratio.round(1)}x degradation)"
170
+ end
171
+ end
172
+ end
173
+
174
+ # Run the benchmark
175
+ if __FILE__ == $0
176
+ StringToNumberBenchmark.run_benchmark
177
+ end