-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlocation.rb
executable file
·180 lines (149 loc) · 4.18 KB
/
location.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# Author:: Robert Dormer (mailto:[email protected])
class LocationNumeral
attr_reader :value
@@values = {}
@@alphabet = {}
#Constructor, which ALWAYS takes ONLY a string
#corresponding to the location number for this object.
#We also have a pair of lookup tables which are useful,
#and these are initialized if they haven't already been.
def initialize(value)
raise ArgumentError unless value.is_a? String
raise ArgumentError unless is_valid_number(value)
@value = value.upcase
if @@values.empty?
start = 'A'
(0..25).each do |i|
@@alphabet[i] = start.clone
@@values[start] = i
start.next!
end
end
end
#location -> integer
#Convert either this object, or, optionally, a passed in string
#corresponding to a location number to it's integer representation.
#Just iterate over the individual digits of the number and multiply
#by their values in the index lookup table
def to_i(s=@value)
raise ArgumentError unless s.is_a? String
bits = s.upcase.chars.to_a
bits.reduce(0) {|sum, current| sum += (2 ** @@values[current])}
end
#integer -> location
#Convert either this object, or, optionally, a passed in integer
#into the corresponding abbreviated location number. We do this
#by converting the number to binary, and then using the bits as
#a mask to determine which digits to pull out of our lookup table
#of letters, with a special case for indices greater than 25.
#Because of the binary conversion the resulting number is
#guaranteed to be in abbreviated form.
def to_abbrev(ivalue=self.to_i)
raise ArgumentError unless ivalue.is_a? Fixnum
binary_rep = ivalue.to_s(2).reverse.each_char.to_a
location = ''
binary_rep.each_with_index do |bit, index|
if bit == '1'
if index < 26
location += @@alphabet[index]
else
location += String.new('ZZ' * ((index - 26) + 1))
end
end
end
location
end
#location -> abbreviated location
#Convert either this object, or an optional passed in string
#corresponding to a location number into it's abbreviated form.
def abbreviate_location(input=@value)
raise ArgumentError unless input.is_a? String
raise ArgumentError unless is_valid_number(input)
int_value = to_i(input)
to_abbrev(int_value)
end
#operators
def +(value)
binary_operator(value) do |op1, op2|
op1 + op2
end
end
#My understanding of location numbers is that
#zero and negative numbers are not possible/allowed,
#so we check for that with subtraction
def -(value)
binary_operator(value) do |op1, op2|
val = op1 - op2
raise 'Underflow' if val <= 0
val
end
end
def *(value)
binary_operator(value) do |op1, op2|
op1 * op2
end
end
#Not entirely sure how you'd divide by zero, since you can't
#make one, but check for it anyway just in case
def /(value)
binary_operator(value) do |op1, divisor|
raise 'Divide by Zero' if divisor.zero?
op1 / divisor
end
end
def **(value)
binary_operator(value) do |base, exp|
base ** exp
end
end
def ==(value)
boolean_operator(value) do |op1, op2|
op1 == op2
end
end
def <(value)
boolean_operator(value) do |op1, op2|
op1 < op2
end
end
def >(value)
boolean_operator(value) do |op1, op2|
op1 > op2
end
end
def <=(value)
boolean_operator(value) do |op1, op2|
op1 <= op2
end
end
def >=(value)
boolean_operator(value) do |op1, op2|
op1 >= op2
end
end
private
def binary_operator(value)
if value.is_a? LocationNumeral
value_1 = self.to_i
value_2 = value.to_i
lvalue = yield value_1, value_2
locvalue = to_abbrev(lvalue)
LocationNumeral.new(locvalue)
else
raise ArgumentError
end
end
def boolean_operator(value)
if value.is_a? LocationNumeral
value_1 = self.to_i
value_2 = value.to_i
yield value_1, value_2
else
raise ArgumentError
end
end
#A valid location number will have letters ONLY
def is_valid_number(num)
num.chars.to_a.all? {|c| c =~ /[A-Z]/i}
end
end