fractional_indexing 0.1.1 → 0.1.2
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 +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
|