fractional_indexing 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -1
- data/fractional_indexing.gemspec +29 -0
- data/lib/fractional_indexing/version.rb +1 -1
- data/lib/fractional_indexing.rb +79 -82
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 764de1e81ced54924f6f5f5c6e5e24a07e31fa761c0b6bc0f7a697ae16676051
|
4
|
+
data.tar.gz: ae443a576b2a888e6ba4af29cd13609629ebcd90eb14ad06c13ed34664e6164a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a0ff4c208d23826268ec6d29e3f53dd8d1b1e9d8420556efd5a5cf520a8d3f08db11b0eb185da48247e512cdf1a5fafb39503d9a004b390030dbf3daf85e27a
|
7
|
+
data.tar.gz: 3bd8c4184d9dbafacfa1055ed17fbad178e08262418b98a4e4edfe652bf7469963ae5e537990c3b6d47ae8f9af0f707ac73a048ea440ace2538f5c5f251f5c63
|
data/Gemfile.lock
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/fractional_indexing/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "fractional_indexing"
|
7
|
+
spec.version = FractionalIndexing::VERSION
|
8
|
+
spec.authors = ["Andreas Alin"]
|
9
|
+
spec.email = ["andreas.alin@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Fractional Indexing in Ruby"
|
12
|
+
spec.homepage = "https://github.com/aalin/fractional_indexing.rb"
|
13
|
+
spec.license = " CC0-1.0"
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
+
spec.files = Dir.chdir(__dir__) do
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
23
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
end
|
data/lib/fractional_indexing.rb
CHANGED
@@ -25,22 +25,16 @@ module FractionalIndexing
|
|
25
25
|
# digits is a string such as '0123456789' for base 10. Digits must be in
|
26
26
|
# ascending character code order!
|
27
27
|
raise Error, "#{a} >= #{b}" if b && a >= b
|
28
|
-
raise Error, "trailing zero" if (
|
28
|
+
raise Error, "trailing zero" if a.end_with?("0") || b&.end_with?("0")
|
29
|
+
|
29
30
|
if b
|
30
31
|
# remove longest common prefix. pad `a` with 0s as we
|
31
32
|
# go. note that we don't need to pad `b`, because it can't
|
32
33
|
# end before `a` while traversing the common prefix.
|
33
|
-
n = 0
|
34
|
-
|
35
|
-
|
36
|
-
if a[i] == b[i]
|
37
|
-
n += 1
|
38
|
-
else
|
39
|
-
break
|
40
|
-
end
|
34
|
+
n = b.each_char.with_index.count { |c, i| (a[i] || "0") == c }
|
35
|
+
if n > 0
|
36
|
+
return b[0...n] + midpoint(a[n..], b[n..], digits)
|
41
37
|
end
|
42
|
-
|
43
|
-
return b[0..(n - 1)] + midpoint(a[n..-1], b[n..-1], digits) if n > 0
|
44
38
|
end
|
45
39
|
|
46
40
|
# first digits (or lack of digit) are different
|
@@ -50,6 +44,7 @@ module FractionalIndexing
|
|
50
44
|
min_digit = (0.5 * (digit_a + digit_b)).round
|
51
45
|
return digits[min_digit]
|
52
46
|
else
|
47
|
+
# first digits are consecutive
|
53
48
|
if b && b.length > 1
|
54
49
|
return b[0]
|
55
50
|
else
|
@@ -59,45 +54,51 @@ module FractionalIndexing
|
|
59
54
|
# given, for example, midpoint('49', '5'), return
|
60
55
|
# '4' + midpoint('9', null), which will become
|
61
56
|
# '4' + '9' + midpoint('', null), which is '495'
|
62
|
-
digit_a
|
63
|
-
return digits[digit_a] + midpoint(a[1..-1], nil, digits)
|
57
|
+
return digits[digit_a] + midpoint(a[1..], nil, digits)
|
64
58
|
end
|
65
59
|
end
|
66
60
|
end
|
67
61
|
|
68
|
-
def self.validate_integer(
|
69
|
-
unless
|
70
|
-
raise Error, "invalid integer part of order key: #{
|
62
|
+
def self.validate_integer(int)
|
63
|
+
unless int.length == get_integer_length(int[0])
|
64
|
+
raise Error, "invalid integer part of order key: #{int}"
|
71
65
|
end
|
72
66
|
end
|
73
67
|
|
74
68
|
def self.get_integer_length(head)
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
69
|
+
case head
|
70
|
+
in 'a'..'z'
|
71
|
+
head.ord - 'a'.ord + 2
|
72
|
+
in 'A'..'Z'
|
73
|
+
'Z'.ord - head.ord + 2
|
74
|
+
else
|
75
|
+
raise Error, "invalid order key head: #{head}"
|
79
76
|
end
|
80
|
-
raise Error, "invalid order key head: " + head
|
81
77
|
end
|
82
78
|
|
83
79
|
def self.get_integer_part(key)
|
84
80
|
integer_part_length = get_integer_length(key[0])
|
85
|
-
raise Error, "invalid order key: #{key}" if integer_part_length > key.size
|
86
81
|
|
87
|
-
key
|
82
|
+
if integer_part_length > key.length
|
83
|
+
raise Error, "invalid order key: #{key}"
|
84
|
+
end
|
85
|
+
|
86
|
+
key.slice(0, integer_part_length)
|
88
87
|
end
|
89
88
|
|
90
89
|
def self.validate_order_key(key)
|
91
|
-
|
90
|
+
if key == SMALLEST_INTEGER
|
91
|
+
raise Error, "invalid order key: #{key}"
|
92
|
+
end
|
92
93
|
|
93
94
|
# get_integer_part() will throw if the first character is bad,
|
94
95
|
# or the key is too short. we'd call it to check these things
|
95
96
|
# even if we didn't need the result
|
96
97
|
i = get_integer_part(key)
|
97
98
|
f = key[i.size..]
|
98
|
-
|
99
|
-
|
100
|
-
|
99
|
+
if f.end_with?("0")
|
100
|
+
raise Error, "invalid order key: #{key}"
|
101
|
+
end
|
101
102
|
end
|
102
103
|
|
103
104
|
def self.increment_integer(x, digits)
|
@@ -126,12 +127,16 @@ module FractionalIndexing
|
|
126
127
|
else
|
127
128
|
digs.pop
|
128
129
|
end
|
129
|
-
|
130
|
+
h + digs.join
|
130
131
|
else
|
131
|
-
|
132
|
+
head + digs.join
|
132
133
|
end
|
133
134
|
end
|
134
135
|
|
136
|
+
def self.increment_integer!(x, digits)
|
137
|
+
increment_integer(x, digits) or raise(Error, "cannot increment anymore")
|
138
|
+
end
|
139
|
+
|
135
140
|
def self.decrement_integer(x, digits)
|
136
141
|
validate_integer(x)
|
137
142
|
head, *digs = x.chars
|
@@ -152,53 +157,61 @@ module FractionalIndexing
|
|
152
157
|
elsif head == 'A'
|
153
158
|
return nil
|
154
159
|
end
|
155
|
-
h =
|
160
|
+
h = head.ord.pred.chr
|
156
161
|
if h < 'Z'
|
157
162
|
digs.push(digits[-1])
|
158
163
|
else
|
159
164
|
digs.pop
|
160
165
|
end
|
161
|
-
|
166
|
+
h + digs.join
|
162
167
|
else
|
163
|
-
|
168
|
+
head + digs.join
|
164
169
|
end
|
165
170
|
end
|
166
171
|
|
172
|
+
def self.decrement_integer!(x, digits)
|
173
|
+
decrement_integer(x, digits) or raise(Error, "cannot decrement anymore")
|
174
|
+
end
|
175
|
+
|
167
176
|
def self.generate_key_between(a, b, digits = BASE_62_DIGITS)
|
168
177
|
validate_order_key(a) if a
|
169
178
|
validate_order_key(b) if b
|
170
|
-
raise "a >= b" if a && b && a >= b
|
179
|
+
raise "#{a.inspect} >= #{b.inspect}" if a && b && a >= b
|
180
|
+
|
181
|
+
unless a
|
182
|
+
return INTEGER_ZERO unless b
|
171
183
|
|
172
|
-
if a == nil
|
173
|
-
return INTEGER_ZERO if b == nil
|
174
184
|
ib = get_integer_part(b)
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
185
|
+
if ib == SMALLEST_INTEGER
|
186
|
+
fb = b[ib.length..]
|
187
|
+
return ib + midpoint("", fb, digits)
|
188
|
+
elsif ib < b
|
189
|
+
return ib
|
190
|
+
else
|
191
|
+
return decrement_integer!(ib, digits)
|
192
|
+
end
|
181
193
|
end
|
182
194
|
|
183
|
-
|
195
|
+
unless b
|
184
196
|
ia = get_integer_part(a)
|
185
|
-
fa = a[ia.length
|
186
|
-
|
187
|
-
return ia + midpoint(fa, nil, digits) if i == nil
|
188
|
-
return i
|
197
|
+
fa = a[ia.length..]
|
198
|
+
return increment_integer(ia, digits) || (ia + midpoint(fa, nil, digits))
|
189
199
|
end
|
190
200
|
|
191
201
|
ia = get_integer_part(a)
|
192
|
-
fa = a[ia.length
|
202
|
+
fa = a[ia.length..]
|
193
203
|
ib = get_integer_part(b)
|
194
|
-
fb = b[ib.length
|
195
|
-
|
196
|
-
|
197
|
-
|
204
|
+
fb = b[ib.length..]
|
205
|
+
|
206
|
+
if ia == ib
|
207
|
+
return ia + midpoint(fa, fb, digits)
|
208
|
+
end
|
209
|
+
|
210
|
+
i = increment_integer!(ia, digits)
|
198
211
|
|
199
212
|
return i if i < b
|
200
213
|
|
201
|
-
|
214
|
+
ia + midpoint(fa, nil, digits)
|
202
215
|
end
|
203
216
|
|
204
217
|
# Returns an array of n distinct keys in sorted order.
|
@@ -206,42 +219,26 @@ module FractionalIndexing
|
|
206
219
|
# If one or the other is nil, returns consecutive "integer"
|
207
220
|
# keys. Otherwise, returns relatively short keys between.
|
208
221
|
def self.generate_n_keys_between(a, b, n, digits = BASE_62_DIGITS)
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
unless b
|
214
|
-
c = generate_key_between(a, b, digits)
|
215
|
-
result = [c]
|
216
|
-
(n - 1).times do
|
217
|
-
c = generate_key_between(c, b, digits)
|
218
|
-
result << c
|
219
|
-
end
|
220
|
-
return result
|
222
|
+
if n == 0
|
223
|
+
return []
|
224
|
+
elsif n == 1
|
225
|
+
return [generate_key_between(a, b, digits)]
|
221
226
|
end
|
222
227
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
(n - 1).times do
|
227
|
-
c = generate_key_between(a, c, digits)
|
228
|
-
result << c
|
229
|
-
end
|
230
|
-
return result.reverse
|
231
|
-
end
|
228
|
+
return n.times.map do
|
229
|
+
a = generate_key_between(a, b, digits)
|
230
|
+
end unless b
|
232
231
|
|
233
|
-
|
232
|
+
return n.times.map do
|
233
|
+
b = generate_key_between(a, b, digits)
|
234
|
+
end.reverse unless a
|
235
|
+
|
236
|
+
mid = (n / 2).floor
|
234
237
|
c = generate_key_between(a, b, digits)
|
235
238
|
[
|
236
|
-
*generate_n_keys_between(a, c, mid
|
239
|
+
*generate_n_keys_between(a, c, mid, digits),
|
237
240
|
c,
|
238
|
-
*generate_n_keys_between(c, b, n - mid
|
241
|
+
*generate_n_keys_between(c, b, n - mid - 1, digits)
|
239
242
|
]
|
240
243
|
end
|
241
|
-
|
242
|
-
# Rounds a float to an integer using decimal.Decimal.quantize with
|
243
|
-
# decimal.ROUND_HALF_UP rounding method.
|
244
|
-
def self.round_half_up(n)
|
245
|
-
(n.to_d.round(0, half: :up)).to_i
|
246
|
-
end
|
247
244
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fractional_indexing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Alin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -22,6 +22,7 @@ files:
|
|
22
22
|
- LICENSE
|
23
23
|
- README.md
|
24
24
|
- Rakefile
|
25
|
+
- fractional_indexing.gemspec
|
25
26
|
- lib/fractional_indexing.rb
|
26
27
|
- lib/fractional_indexing/version.rb
|
27
28
|
- sig/fractional_indexing.rbs
|