base_convert 3.1.191231 → 4.0.200111

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: '09e96875804850531f1e4e890eeac10d8a53275be55d875f3ea262a03624218d'
4
- data.tar.gz: c374bb673c441bb11c314accdcd1b2afef057cf1e23252225cbf08347c83c9ef
3
+ metadata.gz: 8c43a6429ac3a041f6f01f535b4159087a89e1373c95069617bd134b4f0e5e03
4
+ data.tar.gz: 7cbd6306d8ece168d0b5548f1b1e208911be94040d405753c4fb83e1c08af83a
5
5
  SHA512:
6
- metadata.gz: 05ddba6c9079bf3c8a737b8e176e52f560beb6d19ed8aadda1230bd2f09691c112ffbf52cf5b329853f5dd96ab31ade73553938fb83eb7c29783337105bbd31b
7
- data.tar.gz: 2b246aeea822d1aa57b281bc6bc93eb118751c86f938dea9c2eb719741efb53c0ca4e5dc7c26f2d38eb81ee535adc9a8d8cc7226c40de931dd087561f5fa741f
6
+ metadata.gz: 1b1ce6da4bdeb6d589b2b486519b91894eef24502968dc2fa1034e3007704d8791031f89af1e42d92116067c52f50d0b5c2a73ae21ff743975b1b6718d388042
7
+ data.tar.gz: 11cd53a28e2c169186f2c911345c751990ff48843133add22153bb35ff857208b09252f6c117df737601817b4b41d1870ab32eec6678f2e846ee41887cf30d3c
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # BaseConvert
2
2
 
3
+ * [VERSION 4.0.200111](https://github.com/carlosjhr64/base_convert/releases)
3
4
  * [github](https://www.github.com/carlosjhr64/base_convert)
4
5
  * [rubygems](https://rubygems.org/gems/base_convert)
5
6
 
@@ -9,7 +10,7 @@ BaseConvert - Number base conversion.
9
10
 
10
11
  Converts positive integers to different bases:
11
12
  Binary, octal, hexadecimal, decimal, or any arbitrary base.
12
- "Out of the box" handling of up to base 94.
13
+ "Out of the box" handling of up to base 95(:print: characters).
13
14
  Allows for arbitrary choice of alphabet(digits).
14
15
 
15
16
  See also rosettacode.org's [Non-decimal radices convert](http://rosettacode.org/wiki/Non-decimal_radices/Convert).
@@ -21,21 +22,24 @@ See also rosettacode.org's [Non-decimal radices convert](http://rosettacode.org/
21
22
  #toi string, base, digits #=> integer
22
23
  BaseConvert.toi 'FF', 16, '0123456789ABCDEF' #=> 255
23
24
 
24
- #tob integer, base, digits #=> string
25
- BaseConvert.tob 255, 16, '0123456789ABCDEF' #=> "FF"
25
+ #tos integer, base, digits #=> string
26
+ BaseConvert.tos 255, 16, '0123456789ABCDEF' #=> "FF"
26
27
 
27
28
  # FromTo
28
29
  c = BaseConvert::FromTo.new base: 16, digits: '0123456789ABCDEF', to_base: 7, to_digits: 'abcdefg'
29
30
  c['FFF'] #=> "begea"
31
+ c.inspect #=> "16:P95,7:abfg"
30
32
 
31
33
  # Number
32
34
  n = BaseConvert::Number.new 'FF', base: 16, digits: '0123456789ABCDEF'
33
35
  n.to_i #=> 255
34
36
  n.to_s #=> "FF"
37
+ n.inspect #=> FF 16:P95
35
38
  #
36
39
  n = n.to_base 64, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
37
40
  n.to_s #=> "D/"
38
41
  n.to_i #=> 255
42
+ n.inspect #=> D/ 64:B64
39
43
 
40
44
  ## INSTALL:
41
45
 
@@ -43,142 +47,180 @@ See also rosettacode.org's [Non-decimal radices convert](http://rosettacode.org/
43
47
 
44
48
  ## BUT WAIT, THERE'S MORE:
45
49
 
46
- Using `irb` to demonstrate the features.
47
- The components are scoped under `BaseConvert`:
50
+ ### module BaseConvert
51
+
52
+ * `#toi(string=to_s String, base=@base Integer, digits=@digits String) #=> Integer`
53
+ * `#tos(integer=to_i Integer, base=@base Integer, digits=@digits String) #=> String`
54
+ * `#ascii_ordered?(digits=@digits String) #=> TrueClass|FalseClass`
55
+
56
+ Exemplar:
57
+
58
+ class MyClass
59
+ include BaseConvert
60
+ attr_accessor :to_s, :to_i, :base, :digits
61
+ end
62
+
63
+ obj = MyClass.new
64
+ obj.digits = '!@#$%^&*()'
65
+ obj.base = 10
66
+
67
+ obj.to_s = '@'
68
+ obj.toi #=> 1
69
+
70
+ obj.to_i = 3
71
+ obj.tos #=> "$"
72
+
73
+ obj.ascii_ordered? #=> false
74
+ obj.digits = 'ABCDEFGHIJKLMNOP'
75
+ obj.ascii_ordered? #=> true
76
+
77
+ ### Hash DIGITS
78
+
79
+ #### DIGITS methods
80
+
81
+ * `DIGITS.get(key Symbol) #=> String|Symbol|NilClass`
82
+ * `DIGITS.registry(digits=nil NilClass|String) #=> Array(Symbol)|Symbol`
83
+ * `DIGITS.label(digits String) #=> String`
84
+ * `DIGITS.memoize!(key=registry Symbol|Array(Symbol))`
85
+ * `DIGITS.forget!(key=registry Symbol|Array(Symbol))`
86
+
87
+
88
+ Exemplar:
89
+
90
+ include BaseConvert
91
+ DIGITS.get(:P95) #=> :alnum_bangs_typers_operators_separators_scapes_groupers_quoters_spacers
92
+ DIGITS[:P95]
93
+ #=> "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!?$&@*+-/<=>^~,.:;|#\\()[]{}%\"'`_ "
94
+ DIGITS.registry #=> [:P95, :B64, :U47, :G94, :Q91, :W63]
95
+ DIGITS.registry('347') #=> :U47
96
+ DIGITS.registry('0') #=> :P95
97
+ DIGITS.registry('AB') #=> :B64
98
+ DIGITS.registry('Cukoe') #=> nil
99
+ DIGITS.label('Cukoe') #=> :Cuoe
100
+ DIGITS.label('AaBbCcXxYyZz') #=> :AaZz
101
+ DIGITS[:N] #=> "0123456789"
102
+ DIGITS.get(:N) #=> nil
103
+ DIGITS.memoize!(:N)
104
+ DIGITS.get(:N) #=> "0123456789"
105
+ DIGITS.forget!(:N)
106
+ DIGITS.get(:N) #=> nil
107
+
108
+
109
+ #### DIGITS constructions
110
+
111
+ `BaseConvert::DIGITS` will take a `Symbol` representation of `Regexp` patterns.
112
+ See [Ruby-Doc's Regexp](https://ruby-doc.org/core-2.7.0/Regexp.html) documentation
113
+ for a full list of keys. The following provides an exemplar survey:
114
+
115
+ # Character Classes
116
+ # Selected from ASCII 32..126
117
+ DIGITS[:w] #=> "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
118
+ DIGITS[:d] #=> "0123456789"
119
+ # Note: :h was overridden, see :xdigit.
120
+ DIGITS[:h] #=> "0123456789ABCDEF"
121
+ DIGITS[:alpha] #=> "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
122
+ DIGITS[:graph]
123
+ #=> "!\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
124
+ DIGITS[:lower] #=> "abcdefghijklmnopqrstuvwxyz"
125
+ DIGITS[:punct] #=> "!\"\#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
126
+ DIGITS[:upper] #=> "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
127
+ DIGITS[:xdigit] #=> "0123456789ABCDEFabcdef"
128
+
129
+ # Character Properties
130
+ # Selected from ASCII 32..126
131
+ DIGITS[:Alnum] #=> "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
132
+ DIGITS[:Any]
133
+ #=> " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
134
+
135
+ # General Category
136
+ # Selected from ASCII 32..126
137
+ DIGITS[:Ps] #=> "([{"
138
+ DIGITS[:Pe] #=> ")]}"
139
+ DIGITS[:S] #=> "$+<=>^`|~"
140
+
141
+ # Ranged Selections
142
+ # v<hex>w<hex>_<filter>
143
+ DIGITS[:v1d7d8w1d7e1_Any] #=> "𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡"
144
+ # i<dec>j<dec>_<filter>
145
+ DIGITS[:i120488j120513_Any] #=> "𝚨𝚩𝚪𝚫𝚬𝚭𝚮𝚯𝚰𝚱𝚲𝚳𝚴𝚵𝚶𝚷𝚸𝚹𝚺𝚻𝚼𝚽𝚾𝚿𝛀𝛁"
146
+
147
+ # Specified Characters
148
+ # u<hex>
149
+ DIGITS[:u61u62] #=> "ab"
150
+ # k<dec>
151
+ DIGITS[:k97k98] #=> "ab"
152
+
153
+ # BaseConvert's Custom Sets
154
+ DIGITS[:bangs] #=> "!?"
155
+ DIGITS[:typers] #=> "$&@"
156
+ DIGITS[:operators] #=> "*+-/<=>^~"
157
+ DIGITS[:separators] #=> ",.:;|"
158
+ DIGITS[:scapes] #=> "#\\"
159
+ DIGITS[:groupers] #=> "()[]{}"
160
+ DIGITS[:quotes] #=> "\"'`"
161
+ DIGITS[:quoters] #=> "%\"'`"
162
+ DIGITS[:spacers] #=> "_ "
163
+ DIGITS[:ambiguous] #=> "012568BDGIOQSZl"
164
+
165
+ # Composition, add merge:
166
+ DIGITS[:d_ambiguous] #=> "0123456789BDGIOQSZl"
167
+ # Composition, add top:
168
+ DIGITS[:'d+ambiguous'] #=> "3479012568BDGIOQSZl"
169
+ # Composition, subtract:
170
+ DIGITS[:'d-ambiguous'] #=> "3479"
171
+
172
+ # Compositions used in BaseConvert
173
+ # :P95 is:
174
+ DIGITS[:alnum_bangs_typers_operators_separators_scapes_groupers_quoters_spacers]
175
+ #=> "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!?$&@*+-/<=>^~,.:;|#\\()[]{}%\"'`_ "
176
+ # :B64 is:
177
+ DIGITS[:LN_k43k47] #=> "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
178
+ # :U47 is:
179
+ DIGITS[:'alnum-ambiguous'] #=> "3479ACEFHJKLMNPRTUVWXYabcdefghijkmnopqrstuvwxyz"
180
+
181
+ ### class FromTo
182
+
183
+ * `new(base: 10 Integer|Symbol|String, to_base: base, digits: :P95 String|Symbol|Integer, to_digits: digits) #=> FromTo`
184
+ * `#inspect #=> String`
185
+ * `#convert(counter String|Integer) #=> String`
48
186
 
49
- > irb
50
- Welcome to IRB...
51
- >> require 'base_convert' #=> true
52
- >> include BaseConvert #=> Object
53
-
54
- `base_convert` provides three ways to convert a string representation of a number.
55
- The first is functional. One can extend(import) the functions that do the conversions.
56
- The conversion functions are `toi` and `tob`.
57
- For example, the octal number "7777":
58
-
59
- extend BaseConvert #=> main
60
- digits = '01234567'
61
- base = digits.length #=> 8
62
- toi('7777', base, digits) #=> 4095
63
- tob(4095, base, digits) #=> "7777"
64
-
65
- You can work with arbitrary digits:
66
-
67
- digits = ')!@#$%^&'
68
- base = digits.length #=> 8
69
- toi('&&&&', base, digits) #=> 4095
70
- tob(4095, base, digits) #=> "&&&&"
71
-
72
- Note that one can always explicitly specify the ordered digits to be used.
73
- But for convenience, `base_convert` provides some predefined sets of digits:
74
-
75
- * `GRAPH :graph :g`, the ASCII graph characters:
76
-
77
- !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
78
-
79
- * `QGRAPH :qgraph :q`, the ASCII graph characters except `QUOTES`:
80
-
81
- !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_abcdefghijklmnopqrstuvwxyz{|}~
82
-
83
- * `BASE64 :base64 :b64`, the standard base 64 digits from people with no sense of order:
84
-
85
- ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
86
-
87
- * `WORD_ :word_ :w_`, the ASCII word characters including `UNDERSCORE`:
88
-
89
- 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz
90
-
91
- * `WORD :word :w`, the ASCII word characters except `UNDERSCORE`:
92
-
93
- 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
94
-
95
- * `UNAMBIGUOUS :unambigous :u`, the characters in `WORD` without the `AMBIGUOUS` characters(B8G6I1l0OQDS5Z2):
96
-
97
- 3479ACEFHJKLMNPRTUVWXYabcdefghijkmnopqrstuvwxyz
98
-
99
- * `G94 :g94` is the library's default defined as `WORD+QGRAPH.delete(WORD_)+QUOTES+UNDERSCORE`
100
-
101
- 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+,-./:;<=>?@[\]^{|}~"'`_
102
-
103
- Some examples:
104
-
105
- UNAMBIGUOUS #=> "3479ACEFHJKLMNPRTUVWXYabcdefghijkmnopqrstuvwxyz"
106
- # etc...
107
- tob 255, 16, WORD #=> "FF"
108
- tob 255, 64, BASE64 #=> "D/"
109
-
110
- The second way to convert is via a conversion object of `BaseConvert::FromTo`.
111
- For example, to convert from hexadecimal to octal, and back:
112
-
113
- h2o = FromTo.new base: 16, to_base: 8
114
- o2h = FromTo.new base: 8, to_base: 16
115
- h2o['FFFF'] #=> "177777"
116
- o2h['177777'] #=> "FFFF"
117
-
118
- The third way to work with variant base and digits numbers is via the `BaseConvert::Number`:
119
-
120
- hexadecimal = Number.new('FFFF', base: 16, digits: WORD)
121
- hexadecimal.to_s #=> "FFFF"
122
- hexadecimal.to_i #=> 65535
123
-
124
- # Number will infer your most likely meaning:
125
- Number.new('FF').to_i #=> 255
126
-
127
- # But best practice is to fully specify,
128
- # which is easy to do with keys:
129
- n = Number.new 'F', base: :hex, digits: :word
130
- n.to_i #=> 15
131
- n.to_s #=> "F"
132
-
133
- # One can make a change of digits:
134
- n = n.to_digits '0123456789abcdef'
135
- n.to_s #=> "f"
136
- n.to_i #=> 15
137
-
138
- # One can make of change of base:
139
- n = n.to_base 8
140
- n.to_s #=> "17"
141
-
142
- # One can make of change of base and digits:
143
- n = n.to_base 32, :base64
144
- # or vice-versa
145
- n = n.to_digits :base64, 32
146
- n.to_s #=> "P"
147
-
148
- ## Keys (Symbols)
187
+ Example:
149
188
 
150
- Instead of giving the base number or the digits' string,
151
- one can use a mnemonic key:
189
+ h2b = BaseConvert::FromTo.new(base: 16, digits: :P95, to_base: 64, to_digits: :B64)
190
+ h2b #=> 16:P95,64:B64
191
+ h2b['FFF'] #=> "//"
192
+ b2h = BaseConvert::FromTo.new(base: 64, digits: :B64, to_base: 16, to_digits: :P95)
193
+ b2h #=> 64:B64,16:P95
194
+ b2h['//'] #=> "FFF"
152
195
 
153
- | long key | short key | DIGITS | BASE NUMBER |
154
- | -------------- | --------- | ------------- | ----------- |
155
- | `:g94` | | `G94` | 94 |
156
- | `:graph` | `:g` | `GRAPH` | 94 |
157
- | `:qgraph` | `:q` | `QGRAPH` | 91 |
158
- | `:base64` | `:b64` | `BASE64` | 64 |
159
- | `:word_` | `:w_` | `WORD_` | 63 |
160
- | `:word` | `:w` | `WORD` | 62 |
161
- | `:unambiguous` | `:u` | `UNAMBIGUOUS` | 47 |
196
+ ### class Number
162
197
 
163
- | long key | short keys | BASE NUMBER |
164
- | -------------- | ---------- | ----------- |
165
- | `:hexadecimal` | `:hex, :h` | 16 |
166
- | `:decimal` | `:dec, :d` | 10 |
167
- | `:octal` | `:oct, :o` | 8 |
168
- | `:binary` | `:bin, :b` | 2 |
198
+ * `new(counter= 0 Integer|String, base: nil Integer|Symbol|String, digits: nil String|Symbol|Integer, validate: true TrueClass|FalseClass) #=> Number`
199
+ * `#base #=> Integer`
200
+ * `#digits #=> String`
201
+ * `#inspect #=> String`
202
+ * `#to_s #=> String`
203
+ * `#to_i #=> Integer`
204
+ * `#to_base(base Integer|Symbol|String, digits=@digits String|Symbol|Integer, validate=@validate TrueClass|FalseClass) #=> Number`
205
+ * `#to_digits(digits String|Symbol|Integer, base=@base Integer|Symbol|String, validate=@validate TrueClass|FalseClass) #=> Number`
169
206
 
170
207
  Example:
171
208
 
172
- # For some pseudo-random string of unambigous characters
173
- # of very likely length 16:
174
- p = BaseConvert::Number.new(rand(47**16), digits: :u)
175
- p.to_s #=> "CxesjJqHcvpnp7bp"
209
+ a = BaseConvert::Number.new('FFF', base: 16, digits: :P95)
210
+ a #=> FFF 16:P95
211
+ a.to_i #=> 4095
212
+ b = a.to_digits(:U47)
213
+ b #=> RRR 16:U47
214
+ b.to_i #=> 4095
215
+ c = b.to_base(64, :B64)
216
+ c #=> // 64:B64
217
+ c.to_i #=> 4095
176
218
 
177
219
  ## LICENSE:
178
220
 
179
221
  (The MIT License)
180
222
 
181
- Copyright (c) 2019 CarlosJHR64
223
+ Copyright (c) 2020 CarlosJHR64
182
224
 
183
225
  Permission is hereby granted, free of charge, to any person obtaining
184
226
  a copy of this software and associated documentation files (the
@@ -1,6 +1,9 @@
1
1
  module BaseConvert
2
- VERSION = '3.1.191231'
2
+ VERSION = '4.0.200111'
3
3
  require 'base_convert/base_convert'
4
+ require 'base_convert/chars'
5
+ require 'base_convert/digits'
6
+ require 'base_convert/base'
4
7
  require 'base_convert/configuration'
5
8
  require 'base_convert/from_to'
6
9
  require 'base_convert/number'
@@ -0,0 +1,24 @@
1
+ module BaseConvert
2
+ class Base < Hash
3
+ alias :get :[]
4
+ def [](key)
5
+ base = super and return base
6
+ case key
7
+ when String
8
+ base = key.length
9
+ when Integer
10
+ base = key
11
+ when /^\D+(\d+)$/
12
+ base = $1.to_i
13
+ else
14
+ begin
15
+ base = DIGITS[key].length
16
+ rescue
17
+ raise 'unrecognized base key'
18
+ end
19
+ end
20
+ raise 'base must be greater than 1' unless base > 1
21
+ base
22
+ end
23
+ end
24
+ end
@@ -1,6 +1,7 @@
1
1
  # http://rosettacode.org/wiki/Non-decimal_radices/Convert#Ruby
2
2
  module BaseConvert
3
- def toi(string=@string, base=@base, digits=@digits)
3
+ def toi(string=to_s, base=@base, digits=@digits)
4
+ return nil if string.empty?
4
5
  integer = 0
5
6
  string.each_char do |c|
6
7
  index = digits.index(c)
@@ -9,7 +10,8 @@ module BaseConvert
9
10
  integer
10
11
  end
11
12
 
12
- def tob(integer=@integer, base=@base, digits=@digits)
13
+ def tos(integer=to_i, base=@base, digits=@digits)
14
+ return '' if integer.nil?
13
15
  return digits[0] if integer == 0
14
16
  string = ''
15
17
  while integer > 0
@@ -19,5 +21,9 @@ module BaseConvert
19
21
  string
20
22
  end
21
23
 
24
+ def ascii_ordered?(digits=@digits)
25
+ (1..(digits.length-1)).all?{|i|digits[i-1]<digits[i]}
26
+ end
27
+
22
28
  extend self
23
29
  end
@@ -0,0 +1,59 @@
1
+ module BaseConvert
2
+ class Chars < Array
3
+ attr_accessor :start,:stop
4
+ def initialize(start=32, stop=126)
5
+ @start,@stop = start,stop
6
+ end
7
+
8
+ # i<n>: @start=n.to_i
9
+ # v<n>: @start=n.to_i(16)
10
+ # j<n>: @stop=n.to_i
11
+ # w<n>: @stop=n.to_i(16)
12
+ def set(s)
13
+ t,n = s[0],s[1..-1]
14
+ case t
15
+ when 'i','v'
16
+ @start = n.to_i((t=='v')? 16 : 10)
17
+ when 'j','w'
18
+ @stop = n.to_i((t=='w')? 16 : 10)
19
+ else
20
+ raise 'expected /^([ij]\d+)|([vw]\h+)$/'
21
+ end
22
+ end
23
+
24
+ def chars_in(x)
25
+ case x
26
+ when Regexp
27
+ @start.upto(@stop).each do |l|
28
+ c = l.chr(Encoding::UTF_8)
29
+ yield c if x.match? c
30
+ end
31
+ when Symbol
32
+ yield x[1..-1].to_i((x[0]=='u')? 16: 10).chr(Encoding::UTF_8)
33
+ when String
34
+ x.chars.each{|c| yield c}
35
+ when Integer
36
+ yield x.chr(Encoding::UTF_8)
37
+ else
38
+ raise "expected Regexp|Symbol|String|Integer, got #{x.class}"
39
+ end
40
+ end
41
+
42
+ def add(x)
43
+ chars_in(x) do |c|
44
+ self.push(c) unless self.include?(c)
45
+ end
46
+ end
47
+
48
+ def top(x)
49
+ chars_in(x) do |c|
50
+ self.delete(c)
51
+ self.push(c)
52
+ end
53
+ end
54
+
55
+ def remove(x)
56
+ chars_in(x){|c| self.delete(c)}
57
+ end
58
+ end
59
+ end
@@ -1,61 +1,88 @@
1
1
  module BaseConvert
2
- QUOTES = %("'`).freeze
3
- UNDERSCORE = '_'.freeze
4
- AMBIGUOUS = 'B8G6I1l0OQDS5Z2'.freeze
5
-
6
- GRAPH = 0.upto(255).map{|i| i.chr}.select{|c| c=~/[[:graph:]]/}.join.freeze
7
- QGRAPH = GRAPH.delete(QUOTES).freeze
8
- WORD_ = QGRAPH.chars.select{|c| c=~/\w/}.join.freeze
9
- WORD = WORD_.delete(UNDERSCORE).freeze
10
- UNAMBIGUOUS = WORD.delete(AMBIGUOUS).freeze
11
- G94 = (WORD + QGRAPH.delete(WORD_) + QUOTES + UNDERSCORE).freeze
12
-
13
- BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.freeze
14
-
15
- INDEXa = G94.index('a')
16
-
17
- BASE = {
18
- :g94 => G94.length, # 94
19
- :graph => GRAPH.length, # 94
20
- :qgraph => QGRAPH.length, # 91
21
- :word_ => WORD_.length, # 63
22
- :word => WORD.length, # 62
23
- :unambiguous => UNAMBIGUOUS.length, # 47
24
- :base64 => 64,
25
- :b64 => 64,
26
- :hexadecimal => 16,
27
- :hex => 16,
28
- :h => 16,
29
- :decimal => 10,
30
- :dec => 10,
31
- :d => 10,
32
- :octal => 8,
33
- :oct => 8,
34
- :o => 8,
35
- :binary => 2,
36
- :bin => 2,
37
- :b => 2,
38
- }
39
-
40
- BASE[:g] = BASE[:graph]
41
- BASE[:q] = BASE[:qgraph]
42
- BASE[:w_] = BASE[:word_]
43
- BASE[:w] = BASE[:word]
44
- BASE[:u] = BASE[:unambiguous]
45
-
46
- DIGITS = {
47
- :g94 => G94,
48
- :graph => GRAPH,
49
- :g => GRAPH,
50
- :qgraph => QGRAPH,
51
- :q => QGRAPH,
52
- :word_ => WORD_,
53
- :w_ => WORD_,
54
- :word => WORD,
55
- :w => WORD,
56
- :unambiguous => UNAMBIGUOUS,
57
- :u => UNAMBIGUOUS,
58
- :base64 => BASE64,
59
- :b64 => BASE64,
60
- }
2
+ DIGITS = Digits.new
3
+
4
+ # Naming these letter sequences is inpired by
5
+ # (but not the same as)
6
+ # Unicode character’s General Category.
7
+ DIGITS[:bangs] = '!?'.freeze # Used as method name suffix
8
+ DIGITS[:typers] = '$&@'.freeze # Used as variable name prefix
9
+ DIGITS[:operators] = '*+-/<=>^~'.freeze # Used as mathematical operators
10
+ DIGITS[:separators] = ',.:;|'.freeze # Used to separated items
11
+ DIGITS[:scapes] = '#\\'.freeze # Used to escape what's next
12
+ DIGITS[:groupers] = '()[]{}'.freeze # Used to group items
13
+ DIGITS[:quotes] = %("'`).freeze # Quotes
14
+ DIGITS[:quoters] = %(\%"'`).freeze # Used to quote strings('%' not ASCII ordered)
15
+ DIGITS[:spacers] = '_ '.freeze # 1_000 == 1000 #=> true (Not ASCII ordered)
16
+ DIGITS[:ambiguous] = '012568BDGIOQSZl'.freeze # ASCII ordered ambiguous characters
17
+
18
+ ### Recursive string constructors ###
19
+ # 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!?$&@*+-/<=>^~,.:;|#\\()[]{}%"'`_
20
+ DIGITS[:P95] = :alnum_bangs_typers_operators_separators_scapes_groupers_quoters_spacers
21
+ INDEXa = DIGITS[:P95].index('a')
22
+ # 0123456789ABCDEF
23
+ DIGITS[:hexadecimal] = DIGITS[:hex] = DIGITS[:h] = :P16
24
+ # 0123456789
25
+ DIGITS[:decimal] = DIGITS[:dec] = :P10
26
+ # 01234567
27
+ DIGITS[:octal] = DIGITS[:oct] = DIGITS[:o] = :P8
28
+ # 01
29
+ DIGITS[:b] = DIGITS[:bin] = DIGITS[:binary] = :P2
30
+
31
+ # !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
32
+ DIGITS[:G94] = DIGITS[:g] = :graph
33
+
34
+ # !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_abcdefghijklmnopqrstuvwxyz{|}~
35
+ DIGITS[:Q91] = DIGITS[:qgraph] = DIGITS[:q] = :'graph-quotes'
36
+
37
+ # ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
38
+ DIGITS[:base64] = DIGITS[:b64] = DIGITS[:B64] = :LN_k43k47
39
+ DIGITS[:letters] = DIGITS[:l] = :B52 # subset of B64
40
+
41
+ # 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz
42
+ DIGITS[:word] = DIGITS[:W63] = :w
43
+
44
+ # 3479ACEFHJKLMNPRTUVWXYabcdefghijkmnopqrstuvwxyz
45
+ DIGITS[:unambiguous] = DIGITS[:U47] = DIGITS[:u] = :'alnum-ambiguous'
46
+
47
+ BASE = Base[
48
+ # 95
49
+ P95: 95,
50
+ print: 95,
51
+ # 94
52
+ G94: 94, g: 94, graph: 94,
53
+ # 91
54
+ Q91: 91, qgraph: 91, q: 91,
55
+ # 64
56
+ B64: 64, base64: 64, b64: 64,
57
+ # 63
58
+ W63: 63, word: 63, w: 63,
59
+ # 52
60
+ letters: 52, l: 52, L: 52,
61
+ # 47
62
+ U47: 47, unambiguous: 47, u: 47,
63
+ # 16
64
+ hexadecimal: 16, hex: 16, h: 16,
65
+ # 15
66
+ ambiguous: 15,
67
+ # 10
68
+ decimal: 10, dec: 10, d: 10,
69
+ # 9
70
+ operators: 9,
71
+ # 8
72
+ octal: 8, oct: 8, o: 8,
73
+ # 6
74
+ groupers: 6,
75
+ # 5
76
+ separators: 5,
77
+ # 4
78
+ quoters: 4,
79
+ # 3
80
+ typers: 3,
81
+ quotes: 3,
82
+ # 2
83
+ binary: 2, bin: 2, b: 2,
84
+ bangs: 2,
85
+ scapes: 2,
86
+ spacers: 2,
87
+ ]
61
88
  end
@@ -0,0 +1,96 @@
1
+ module BaseConvert
2
+ class Digits < Hash
3
+ alias :get :[]
4
+ def [](key)
5
+ if self.has_key?(key)
6
+ d = super(key)
7
+ return d.is_a?(Symbol)? self[d]: d
8
+ end
9
+ case key
10
+ when Symbol
11
+ chars = Chars.new
12
+ key.to_s.scan(/[+-]?[[:alnum:]]+/).each do |type|
13
+ if self.has_key?(_=type.to_sym)
14
+ chars.add super(_)
15
+ next
16
+ end
17
+ case type
18
+ when /^((u\h+)|(k\d+))+$/
19
+ type.scan(/[uk]\h+/).each{|s| chars.top s.to_sym}
20
+ when /^(([ij]\d+)|([vw]\h+))+$/
21
+ type.scan(/[ijvw]\h+/).each{|s| chars.set s}
22
+ when /^[a-z][a-z]+$/
23
+ chars.add Regexp.new "[[:#{type}:]]"
24
+ when /^[a-z]$/
25
+ chars.add Regexp.new "\\#{type}"
26
+ when /^[A-Z]+$/i
27
+ type.scan(/[A-Z][a-z]*/).each{|property| chars.add /\p{#{property}}/}
28
+ when /^([+-])(\w+)/
29
+ d = self[$2.to_sym]
30
+ case $1
31
+ when '+'
32
+ chars.top d
33
+ when '-'
34
+ chars.remove d
35
+ end
36
+ when /^(\p{L}+)(\d+)$/
37
+ l,m = $1,$2.to_i-1
38
+ n = self.keys.select{|_|_=~/^#{l}\d+$/}.map{|_|_.to_s.sub(l,'').to_i}.max
39
+ raise "no #{l}<n> digits defined" if n.nil?
40
+ raise "out of range of #{l}#{n}" unless m<n
41
+ chars.add self[:"#{l}#{n}"][0..m]
42
+ else
43
+ raise "unrecognized digits key: #{type}"
44
+ end
45
+ end
46
+ return chars.uniq.join.freeze
47
+ when String
48
+ digits = nil # set as a side effect...
49
+ unless registry.detect{|_|(digits=self[_]).start_with? key}
50
+ # ...here -------------->^^^^^
51
+ raise 'need at least 2 digits' unless key.length > 1
52
+ raise 'digits must not have duplicates' if key.length > key.chars.uniq.length
53
+ return key
54
+ end
55
+ return digits
56
+ when Integer
57
+ raise 'need digits to cover base' if key > 95
58
+ return self[:P95] # Defined below
59
+ end
60
+ raise 'digits must be String|Symbol|Integer'
61
+ end
62
+
63
+ def registry(d=nil)
64
+ # BaseConvert::Number memoizes and uses specifically :P95, :B64, and :U47;
65
+ # giving these precedence above the rest.
66
+ @registry ||= [:P95, :B64, :U47, :G94, :Q91, :W63]
67
+ d ? @registry.detect{|_|self[_].start_with? d}: @registry
68
+ end
69
+
70
+ def label(d)
71
+ registry(d) or (d[0]+d[1]+d[-2]+d[-1]).to_sym
72
+ end
73
+
74
+ def memoize!(keys=registry)
75
+ [*keys].each do |k|
76
+ while s = get(k)
77
+ break if s.is_a? String # links to a constructed String
78
+ raise 'expected Symbol' unless s.is_a? Symbol
79
+ k = s
80
+ end
81
+ self[k]=self[k] if s.nil? # if not memoized, memoize!
82
+ end
83
+ end
84
+
85
+ def forget!(keys=registry)
86
+ [*keys].each do |k|
87
+ while s = get(k)
88
+ break if s.is_a? String # links to a constructed String
89
+ raise 'expected Symbol' unless s.is_a? Symbol
90
+ k = s
91
+ end
92
+ self.delete(k) if s.is_a? String
93
+ end
94
+ end
95
+ end
96
+ end
@@ -2,23 +2,29 @@ module BaseConvert
2
2
  class FromTo
3
3
  include BaseConvert
4
4
 
5
- def initialize(base: 10, to_base: base, digits: G94, to_digits: digits)
6
- base = BASE[base] if base.is_a? Symbol
7
- to_base = BASE[to_base] if to_base.is_a? Symbol
8
- digits = DIGITS[digits] if digits.is_a? Symbol
9
- to_digits = DIGITS[to_digits] if to_digits.is_a? Symbol
10
- raise "base must cover digits." if base > digits.length or to_base > to_digits.length
5
+ def initialize(base: 10, to_base: base, digits: :P95, to_digits: digits)
6
+ base = BASE[base]
7
+ to_base = BASE[to_base]
8
+ digits = DIGITS[digits]
9
+ to_digits = DIGITS[to_digits]
10
+ raise 'base must cover digits' if base > digits.length or to_base > to_digits.length
11
11
  @base, @to_base, @digits, @to_digits = base, to_base, digits, to_digits
12
12
  end
13
+
14
+ def inspect
15
+ d0 = DIGITS.label(@digits)
16
+ d1 = DIGITS.label(@to_digits)
17
+ "#{@base}:#{d0},#{@to_base}:#{d1}"
18
+ end
13
19
 
14
20
  def convert(counter)
15
21
  case counter
16
22
  when Integer
17
- tob(counter, @to_base, @to_digits)
23
+ tos(counter, @to_base, @to_digits)
18
24
  when String
19
- tob(toi(counter), @to_base, @to_digits)
25
+ tos(toi(counter), @to_base, @to_digits)
20
26
  else
21
- raise "counter must be String|Integer."
27
+ raise 'counter must be String|Integer'
22
28
  end
23
29
  end
24
30
  alias :[] :convert
@@ -1,23 +1,27 @@
1
1
  module BaseConvert
2
2
  class Number
3
3
  include BaseConvert
4
+ DIGITS.memoize!
4
5
 
5
6
  def self.infer(string)
6
- return 2, G94 if string.empty?
7
+ p95 = DIGITS[:P95]
8
+ return 2, p95 if string.empty?
7
9
  chars = string.chars
8
- raise 'Need digits.' unless chars.all?{|_|G94.include?_}
9
- max = chars.map{|_|G94.index(_)}.max
10
- return 2, G94 if max < 2
11
- return 4, G94 if max < 4
12
- return 8, G94 if max < 8
13
- return 10, G94 if max < 10
14
- return 16, G94 if max < 16
15
- return 32, G94 if max < 32
16
- [UNAMBIGUOUS, BASE64].each do |digits|
17
- return digits.length, digits if chars.all?{|_|digits.include?_}
18
- end
19
- return 64, G94 if max < 64
20
- return G94.length, G94 if max < 64
10
+ raise 'need digits to cover string' unless chars.all?{|_|p95.include?_}
11
+ max = chars.map{|_|p95.index(_)}.max
12
+ return 95, p95 if max == 94 # string has a space digit.
13
+ return 2, p95 if max < 2
14
+ return 4, p95 if max < 4
15
+ return 8, p95 if max < 8
16
+ return 10, p95 if max < 10
17
+ return 16, p95 if max < 16
18
+ return 32, p95 if max < 32
19
+ u47 = DIGITS[:U47]
20
+ return 47, u47 if chars.all?{|_|u47.include?_}
21
+ return 64, p95 if max < 64
22
+ b64 = DIGITS[:B64]
23
+ return 64, b64 if chars.all?{|_|b64.include?_}
24
+ return 94, p95
21
25
  end
22
26
 
23
27
  attr_reader :base, :digits
@@ -27,65 +31,62 @@ class Number
27
31
  when true, false
28
32
  @validate = validate
29
33
  else
30
- raise "validate must be either true of false."
34
+ raise 'validate must be either true or false'
31
35
  end
32
36
 
33
37
  # counter
38
+ string = nil
34
39
  case counter
35
40
  when String
36
- @string = counter
41
+ string = counter
37
42
  base, digits = Number.infer(counter) if base.nil? and digits.nil?
38
43
  when Integer
39
44
  @integer = counter
40
- base, digits = 10, G94 if base.nil? and digits.nil?
45
+ base, digits = 10, DIGITS[:P95] if base.nil? and digits.nil?
41
46
  else
42
- raise "Need counter String|Integer."
47
+ raise 'need counter String|Integer'
43
48
  end
44
49
 
45
50
  # digits
46
- if digits.is_a? Symbol
47
- digits = DIGITS[digits]
48
- raise "Unrecognized digits." if digits.nil?
49
- end
50
- digits = DIGITS[base] if digits.nil?
51
- digits = G94 if digits.nil?
52
- raise "digits must be a String of at least length 2." unless digits.is_a?(String) and digits.length > 1
53
- @digits = digits
51
+ @digits = DIGITS[digits || base]
54
52
 
55
53
  # base
56
- if base.is_a? Symbol
57
- base = BASE[base]
58
- raise "Unrecognized base." if base.nil?
59
- end
60
- base = @digits.length if base.nil?
61
- raise "base must be an Integer greater than 1." unless base.is_a?(Integer) and base > 1
62
- @base = base
54
+ base = digits if base.nil? and digits.is_a? Symbol
55
+ @base = BASE[base || @digits.length]
63
56
 
64
57
  # validate
65
58
  if @validate
66
- raise "digits must cover base." if @base > @digits.length
67
- raise "digits must not have duplicates." if @digits.length > @digits.chars.uniq.length
68
- unless @string.nil? or @string.empty?
69
- @string.upcase! if @base <= INDEXa and @digits.equal? G94
70
- raise "digits must cover string." unless @string.chars.all?{|_|@digits.include?_}
71
- raise "digits in string must be under base." unless @base > @string.chars.map{|_|@digits.index(_)}.max
59
+ raise 'digits must cover base' if @base > @digits.length
60
+ unless string.nil? or string.empty?
61
+ indeces = string.chars.map{|_|@digits.index(_)}
62
+ if missing = indeces.any?{|_|_.nil?} or exceeding = indeces.any?{|_|_>=@base}
63
+ if @base <= INDEXa and DIGITS[:P95].start_with?(@digits)
64
+ string = string.upcase
65
+ indeces = string.chars.map{|_|@digits.index(_)}
66
+ missing = indeces.any?{|_|_.nil?} or exceeding = indeces.any?{|_|_>=@base}
67
+ end
68
+ raise 'digits must cover string' if missing
69
+ raise 'digits in string must be under base' if exceeding
70
+ end
72
71
  end
73
72
  unless @integer.nil?
74
- raise "integer can't be negative." if @integer < 0
73
+ raise 'integer can not be negative' if @integer < 0
75
74
  end
76
75
  end
77
76
 
78
- @integer = toi if @integer.nil?
79
- @string = tob if @string.nil?
77
+ @integer = toi(string) if @integer.nil?
78
+ end
79
+
80
+ def inspect
81
+ d = DIGITS.label(@digits)
82
+ "#{to_s} #{@base}:#{d}"
80
83
  end
81
84
 
82
85
  def validate?
83
86
  @validate
84
87
  end
85
88
 
86
- def to_s
87
- @string
88
- end
89
+ alias to_s tos
89
90
 
90
91
  def to_i
91
92
  @integer
metadata CHANGED
@@ -1,21 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: base_convert
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.191231
4
+ version: 4.0.200111
5
5
  platform: ruby
6
6
  authors:
7
7
  - carlosjhr64
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-31 00:00:00.000000000 Z
11
+ date: 2020-01-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  BaseConvert - Number base conversion.
15
15
 
16
16
  Converts positive integers to different bases:
17
17
  Binary, octal, hexadecimal, decimal, or any arbitrary base.
18
- "Out of the box" handling of up to base 94.
18
+ "Out of the box" handling of up to base 95(:print: characters).
19
19
  Allows for arbitrary choice of alphabet(digits).
20
20
  email: carlosjhr64@gmail.com
21
21
  executables: []
@@ -24,8 +24,11 @@ extra_rdoc_files: []
24
24
  files:
25
25
  - README.md
26
26
  - lib/base_convert.rb
27
+ - lib/base_convert/base.rb
27
28
  - lib/base_convert/base_convert.rb
29
+ - lib/base_convert/chars.rb
28
30
  - lib/base_convert/configuration.rb
31
+ - lib/base_convert/digits.rb
29
32
  - lib/base_convert/from_to.rb
30
33
  - lib/base_convert/number.rb
31
34
  homepage: https://github.com/carlosjhr64/base_convert