zen_to_i 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
  SHA256:
3
- metadata.gz: b7159a12adbda141423184a4769d10011e872607f7504df9f10ff47146d25880
4
- data.tar.gz: 8b34203aaad6483c34acf49d7f7e3b07972a0dec470a56cfef3598156c9ab22d
3
+ metadata.gz: 2caafd58ade6791a65d28c27e8e11d0ad4c67bb24d89640b9b7d1fc80831844a
4
+ data.tar.gz: dbe72c43236b37f92064779d2a3637d4945fc3c7536ee83e68c2e2d0a8f2166c
5
5
  SHA512:
6
- metadata.gz: 66e00c3a2e43a09b7cf8e8cfa78d504fd798a8403c1f787ad4e03f7a34bd1ff05d2ac4033e80405cba4fe0a8b77c09c4fca7b656751c912ea067789f81ba0a56
7
- data.tar.gz: e2a84957f2e08f246c001bbfd94b4bfc1e1bbe56d2a8a1c21a51d14684d072e394060a912be352ca6a938140975b8dd79c3aeeea888abebb9e431dbddea36042
6
+ metadata.gz: 95264694be883ae9fb324a90fd4fb61047f1ff6a117aa3aab8aed04d7586f9417c7c222a29a807cd1e2853b4d81726d8543de1a26321c3ed5dffe41dda6ff37a
7
+ data.tar.gz: 78c65fc1b572ccd8983c5115eb79b536e10c8d7e34ffa3f664928573ceb9c746c124bb928fbbe01c920ebafbbd9ea8b52feb0ffad3a41c68ed69c6cd2ba93a8d
data/README.md CHANGED
@@ -3,36 +3,36 @@
3
3
 
4
4
  [![Actions Status](https://github.com/yoshitsugu/zen_to_i/workflows/test/badge.svg)](https://github.com/yoshitsugu/zen_to_i/actions)
5
5
 
6
- ## Installation
6
+ ## インストール
7
7
 
8
- Install via RubyGems.
8
+ RubyGems からインストールします。
9
9
 
10
10
  $ gem install zen_to_i
11
11
 
12
- Or, use Bundler. Add this line to your application's Gemfile:
12
+ または Bundler を使います。アプリケーションの Gemfile に次の行を追加してください。
13
13
 
14
14
  ```ruby
15
15
  gem "zen_to_i"
16
16
  ```
17
17
 
18
- Then run:
18
+ その後、次を実行します。
19
19
 
20
20
  $ bundle
21
21
 
22
- ## Usage
22
+ ## 使い方
23
23
 
24
- ### 1. Use the `zen_to_i` command from your shell
24
+ ### 1. シェルから `zen_to_i` コマンドを使う
25
25
 
26
26
  $ zen_to_i "一二三"
27
27
  123
28
28
 
29
- # or use a pipe
29
+ # パイプで渡すこともできます
30
30
  $ echo "一二三" | zen_to_i
31
31
  123
32
32
 
33
- Please run `bundle exec zen_to_i` if you installed the gem via Bundler.
33
+ Bundler経由で gem をインストールした場合は `bundle exec zen_to_i` を実行してください。
34
34
 
35
- ### 2. Use it in your Ruby script
35
+ ### 2. Ruby スクリプト内で使う
36
36
 
37
37
  ```ruby
38
38
  require 'zen_to_i'
@@ -49,9 +49,24 @@ require 'zen_to_i'
49
49
  "三兆五十二万四十八".zen_to_i
50
50
  #=> "3000000520048"
51
51
  ```
52
- Note that `zen_to_i` returns a String, not an Integer.
52
+ `zen_to_i` は Integer ではなく String を返すことに注意してください。
53
53
 
54
- You can also override `to_i` with a refinement. The override is active only in the lexical scope where `using ZenToI::Refine` appears, and returns an Integer like the built-in `to_i` method.
54
+ 位(十・百・千)が大きい順に並ぶ場合は同じ数の桁として解釈します。同じ位が重なったり小さい位から大きい位へ並んだりした場合は、別の数として連結します。
55
+
56
+ ```ruby
57
+ "百十".zen_to_i
58
+ #=> "110"
59
+
60
+ "十十".zen_to_i
61
+ #=> "1010"
62
+
63
+ "十百".zen_to_i
64
+ #=> "10100"
65
+ ```
66
+
67
+ 万・億・兆の並びは従来どおりセクションの加算として扱います(`一億一億` は `200000000`)。位記号を含まない漢数字だけの並び(例: `〇〇七`)は、先頭のゼロを落とさずそのまま連結します。
68
+
69
+ refinement で `to_i` を上書きすることもできます。上書きは `using ZenToI::Refine` が書かれた語彙スコープ内だけで有効で、組み込みの `to_i` メソッドと同様に Integer を返します。
55
70
 
56
71
  ```ruby
57
72
  require "zen_to_i/refine"
@@ -65,7 +80,7 @@ using ZenToI::Refine
65
80
  #=> 123
66
81
  ```
67
82
 
68
- You can also replace `String#to_i` globally by requiring `zen_to_i/string_ext`. This affects all strings in the process; the original `to_i` implementation remains available as `String#_to_i`. Use this only when a global override is intended.
83
+ `zen_to_i/string_ext` を require すると `String#to_i` をプロセス全体で置き換えることもできます。これはプロセス内のすべての文字列に影響します。元の `to_i` の実装は `String#_to_i` として残ります。グローバルな上書きが意図している場合にのみ使ってください。
69
84
 
70
85
  ```ruby
71
86
  require "zen_to_i/string_ext"
@@ -77,6 +92,16 @@ require "zen_to_i/string_ext"
77
92
  #=> 123
78
93
  ```
79
94
 
95
+ 基数を指定した `to_i` も使えます。基数は漢数字を変換した後の文字列に適用されるため、漢数字と基数を組み合わせると直感と異なる値になります。
96
+
97
+ ```ruby
98
+ "十六".to_i(16)
99
+ #=> 22 # 十六 は 16 ではない
100
+
101
+ "一〇".to_i(2)
102
+ #=> 2
103
+ ```
104
+
80
105
 
81
106
  ## Contributing
82
107
 
@@ -12,11 +12,11 @@ module ZenToI
12
12
  KANORDER_F = { "万" => 10**4, "億" => 10**8, "兆" => 10**12,
13
13
  "" => 1, "萬" => 10**4 }.freeze
14
14
  ALL_KANSUUJI = KANNUM.keys + KANORDER.keys + KANORDER_F.keys
15
- MAX_LOOP = 100
16
- KANORDER_F_KEYS_REGEX = /([^#{KANORDER_F.keys.join("")}]*)([#{KANORDER_F.keys.join("")}]?)/
17
- KANORDER_KEYS_REGEX = /([^#{KANORDER.keys.join("")}]*)([#{KANORDER.keys.join("")}]?)/
18
- ALL_KANSUUJI_REGEX = /([#{ALL_KANSUUJI.join("")}]+)/
19
- KANNUM_PAIR_REGEX = /([#{KANNUM.keys.join("")}])([#{KANNUM.keys.join("")}])/
15
+ ASCII_DIGITS = (0..9).map(&:to_s)
16
+ # 半角数字が漢数字ブロックに混ざる場合(例: 3万)も漢数字側として扱う
17
+ ALL_KANSUUJI_REGEX = /([#{ALL_KANSUUJI.join("")}#{ASCII_DIGITS.join("")}]+)/
18
+ # 半角数字だけのブロック(例: 007)は数値として評価せず素通しする
19
+ ASCII_DIGITS_ONLY_REGEX = /\A[0-9]+\z/
20
20
  end
21
21
  end
22
22
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "constant"
4
+ require_relative "parser"
4
5
 
5
6
  module ZenToI
6
7
  module Mixin
@@ -14,8 +15,7 @@ module ZenToI
14
15
  private
15
16
 
16
17
  def kanji2num(str)
17
- array = split_kansuuji(str)
18
- array = split_kansuuji_detail(array)
18
+ array = split_kansuuji(zen2han(str))
19
19
  result = convert_kansuuji(array)
20
20
  result.join("")
21
21
  end
@@ -29,55 +29,15 @@ module ZenToI
29
29
  string.split(ALL_KANSUUJI_REGEX)
30
30
  end
31
31
 
32
- # 位を表す数ではない普通の漢数字が続いていたらわける
33
- def split_kansuuji_detail(array)
34
- array_tmp = array.clone
35
- array_result = array.clone
36
- (1..MAX_LOOP).each do |_i|
37
- array_result = split_kansuuji_detail_inner(array_result)
38
- return array_result if array_result == array_tmp
39
-
40
- array_tmp = array_result.clone
41
- end
42
- array_result
43
- end
44
-
45
- def split_kansuuji_detail_inner(array)
46
- ary = array.map do |a|
47
- if ALL_KANSUUJI_REGEX.match?(a)
48
- a.gsub(KANNUM_PAIR_REGEX, '\1,\2').split(",")
49
- else
50
- a
51
- end
52
- end
53
- ary.flatten
54
- end
55
-
56
- # 4桁ごとの単位(万、億、兆など)でまずわけ、
57
- # それぞれに対して漢数字→数字を実行している。
32
+ # 漢数字ブロックだけを数値化する。半角数字だけのブロック(007 など)は素通しする。
58
33
  def convert_kansuuji(array)
59
34
  array.map do |a|
60
- if ALL_KANSUUJI_REGEX.match?(a)
61
- sum_kansuuji(a)
62
- else
63
- a
64
- end
65
- end
66
- end
67
-
68
- def sum_kansuuji(str)
69
- str.scan(KANORDER_F_KEYS_REGEX).inject(0) do |ret, (unit, order_f)|
70
- ret + (unit_to_i(unit, order_f) * KANORDER_F[order_f].to_i)
35
+ kansuuji_block?(a) ? ::ZenToI::Parser.call(a) : a
71
36
  end
72
37
  end
73
38
 
74
- # 大字だけの表記は 1 個分として数え、scan の末尾の空一致は桁を持たないので 0 とする。
75
- def unit_to_i(unit, order_f)
76
- return order_f.empty? ? 0 : 1 if unit.empty?
77
-
78
- unit.scan(KANORDER_KEYS_REGEX).inject(-1) do |tmp, (kanji, order)|
79
- tmp + (KANNUM[kanji].to_i * KANORDER[order].to_i)
80
- end
39
+ def kansuuji_block?(str)
40
+ !ASCII_DIGITS_ONLY_REGEX.match?(str) && ALL_KANSUUJI_REGEX.match?(str)
81
41
  end
82
42
  end
83
43
  end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constant"
4
+
5
+ module ZenToI
6
+ # 漢数字ブロック(例: "三兆五十二万四十八")を整数に変換する。
7
+ module Parser
8
+ include ::ZenToI::Constant
9
+
10
+ module_function
11
+
12
+ # 位記号(十百千万億兆)を含まない数字だけのブロックは、先頭ゼロを保持するため
13
+ # 数値化せず各桁をそのまま連結する(例: 〇〇七 => "007")。
14
+ def call(str)
15
+ return digit_string(str) unless order_marker?(str)
16
+
17
+ chunks = tokenize(str).slice_after { |kind, _| kind == :order_f }
18
+ chunks.sum do |chunk|
19
+ body, multiplier = split_chunk(chunk)
20
+ section_value(body, multiplier)
21
+ end.to_s
22
+ end
23
+
24
+ def digit_string(str)
25
+ tokenize(str).map { |_kind, value| value.to_s }.join
26
+ end
27
+
28
+ # 万・億・兆で終わるまとまりは、その単位を乗数として切り離す。
29
+ def split_chunk(chunk)
30
+ last = chunk.last
31
+ return [chunk, 1] unless last.first == :order_f
32
+
33
+ [chunk[0..-2], last.last]
34
+ end
35
+
36
+ # 単位単体(万など)は 1 個分として数える。
37
+ # セクション内は「位のあとの数字2桁目以降で分割」してから各チャンクを評価し連結する。
38
+ def section_value(tokens, multiplier)
39
+ return multiplier if tokens.empty?
40
+
41
+ split_after_order_tail(tokens).map { |chunk| eval_section(chunk) }.join.to_i * multiplier
42
+ end
43
+
44
+ # 位記号(十百千)または大単位(万億兆)を含むか。
45
+ def order_marker?(str)
46
+ str.each_char.any? { |char| KANORDER.key?(char) || KANORDER_F.key?(char) }
47
+ end
48
+
49
+ # 半角数字の連続は1つの数として扱う(例: 3千500 の "500")。
50
+ def tokenize(str)
51
+ str.scan(/[0-9]+|./).filter_map do |char|
52
+ kind, value = token_of(char)
53
+ [kind, value] if kind
54
+ end
55
+ end
56
+
57
+ def token_of(char)
58
+ if KANNUM.key?(char)
59
+ [:num, KANNUM[char]]
60
+ elsif ASCII_DIGITS_ONLY_REGEX.match?(char)
61
+ [:num, char.to_i]
62
+ elsif KANORDER.key?(char)
63
+ [:order, KANORDER[char]]
64
+ elsif KANORDER_F.key?(char)
65
+ [:order_f, KANORDER_F[char]]
66
+ end
67
+ end
68
+
69
+ # 位のあとに続く数字は1桁だけ同じ数に入れ、2桁目以降は別の数として分割する。
70
+ # (例: 百二三十 => 百二 | 三十 => "102" + "30" = "10230")
71
+ def split_after_order_tail(tokens)
72
+ seen_order = false
73
+ tokens.slice_when do |before, after|
74
+ cut = seen_order && before.first == :num && after.first == :num
75
+ seen_order ||= before.first == :order
76
+ cut
77
+ end
78
+ end
79
+
80
+ # 位が小さくなる間は同じ数の桁として加算し、同じ位か大きい位が来たら別の数として連結する。
81
+ def eval_section(tokens)
82
+ state = { completed: [], current: 0, last_order: nil, pending: nil }
83
+ tokens.each { |token| apply_token(state, token) }
84
+ finish_section(state)
85
+ end
86
+
87
+ def apply_token(state, (kind, value))
88
+ case kind
89
+ when :num
90
+ apply_num(state, value)
91
+ when :order
92
+ apply_order(state, value)
93
+ end
94
+ end
95
+
96
+ def apply_num(state, value)
97
+ state[:pending] = state[:pending].nil? ? value : (state[:pending] * 10) + value
98
+ end
99
+
100
+ def apply_order(state, value)
101
+ coefficient = state[:pending].nil? ? 1 : state[:pending]
102
+ if state[:last_order].nil? || value < state[:last_order]
103
+ state[:current] += coefficient * value
104
+ else
105
+ state[:completed] << state[:current]
106
+ state[:current] = coefficient * value
107
+ end
108
+ state[:last_order] = value
109
+ state[:pending] = nil
110
+ end
111
+
112
+ def finish_section(state)
113
+ state[:current] += state[:pending] unless state[:pending].nil?
114
+ state[:completed] << state[:current]
115
+ state[:completed].join.to_i
116
+ end
117
+ end
118
+ end
data/lib/zen_to_i/to_i.rb CHANGED
@@ -9,8 +9,8 @@ module ZenToI
9
9
  # ネイティブ実装を捕捉し、どのロード順でも素の to_i を参照できるようにする。
10
10
  ORIGINAL_TO_I = String.instance_method(:to_i) unless const_defined?(:ORIGINAL_TO_I, false)
11
11
 
12
- def to_i
13
- ORIGINAL_TO_I.bind(zen_to_i).call
12
+ def to_i(*)
13
+ ORIGINAL_TO_I.bind(zen_to_i).call(*)
14
14
  end
15
15
 
16
16
  def _to_i(*)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZenToI
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zen_to_i
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
  - yoshitsugu
@@ -23,6 +23,7 @@ files:
23
23
  - lib/zen_to_i.rb
24
24
  - lib/zen_to_i/constant.rb
25
25
  - lib/zen_to_i/mixin.rb
26
+ - lib/zen_to_i/parser.rb
26
27
  - lib/zen_to_i/refine.rb
27
28
  - lib/zen_to_i/string_ext.rb
28
29
  - lib/zen_to_i/to_i.rb