base_convert 3.1.191231 → 4.0.200111

Sign up to get free protection for your applications and to get access to all the features.
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