redis_recipes 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |s|
3
+ s.platform = Gem::Platform::RUBY
4
+ s.required_ruby_version = '>= 1.9.0'
5
+ s.required_rubygems_version = ">= 1.3.6"
6
+
7
+ s.name = "redis_recipes"
8
+ s.summary = "Redis LUA recipes."
9
+ s.description = "Require Redis 2.6.0 or higher"
10
+ s.version = "0.3.0"
11
+
12
+ s.authors = ["Black Square Media"]
13
+ s.email = "info@blacksquaremedia.com"
14
+ s.homepage = "https://github.com/bsm/redis_recipes"
15
+
16
+ s.require_path = ['lib']
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- spec/*`.split("\n")
19
+
20
+ s.add_development_dependency "redis", "~> 3.0.0"
21
+ s.add_development_dependency "rake"
22
+ s.add_development_dependency "rspec"
23
+ end
@@ -0,0 +1,146 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Range Lookup: Adding" do
4
+
5
+ def state(c = :to_i)
6
+ ranges = {}
7
+ redis.zrange('my_store:~', 0, -1, with_scores: true).each do |value, score|
8
+ ranges[score.send(c)] = redis.smembers("my_store:#{value}").sort.join
9
+ end
10
+ ranges
11
+ end
12
+
13
+ def add(member, min, max)
14
+ evalsha :add, keys: ["my_store"], argv: [member, min, max]
15
+ end
16
+
17
+ describe "general" do
18
+
19
+ it 'should catch invalid arguments' do
20
+ lambda { evalsha :add }.should raise_error(Redis::CommandError, /wrong number of arguments/)
21
+ lambda { evalsha :add, keys: ["one"] }.should raise_error(Redis::CommandError, /wrong number of arguments/)
22
+ lambda { evalsha :add, keys: ["one"], argv: ["one", 2] }.should raise_error(Redis::CommandError, /wrong number of arguments/)
23
+ lambda { evalsha :add, keys: ["one", "two"], argv: ["one", 2, 3] }.should raise_error(Redis::CommandError, /wrong number of arguments/)
24
+ end
25
+
26
+ it 'should catch non-numeric ranges' do
27
+ lambda { evalsha :add, keys: ["one"], argv: ["one", "two", 3] }.should raise_error(Redis::CommandError, /not numeric or out of range/)
28
+ lambda { evalsha :add, keys: ["one"], argv: ["one", 2, "three"] }.should raise_error(Redis::CommandError, /not numeric or out of range/)
29
+ lambda { evalsha :add, keys: ["one"], argv: ["one", 3, 2] }.should raise_error(Redis::CommandError, /not numeric or out of range/)
30
+ end
31
+
32
+ end
33
+
34
+ describe "adding" do
35
+ before { add("A", 8, 17) }
36
+
37
+ it 'should support single-value ranges' do
38
+ lambda { add("B", 10, 10) }.should change { state }.
39
+ to 8 => "A", 10 => "AB", 17 => "A"
40
+ end
41
+
42
+ it 'should support float/decimal ranges' do
43
+ lambda { add("B", 10, 10.5) }.should change { state(:to_f) }.
44
+ to 8.0 => "A", 10.0 => "AB", 10.5 => "AB", 17.0 => "A"
45
+ end
46
+
47
+ it 'should support zero bounds' do
48
+ lambda { add("B", 0, 5) }.should change { state }.
49
+ to 0 => "B", 5 => "B", 8 => "A", 17 => "A"
50
+
51
+ lambda { add("C", -5, 0) }.should change { state }.
52
+ to -5 => "C", 0 => "BC", 5 => "B", 8 => "A", 17 => "A"
53
+ end
54
+
55
+ it 'should maintain an index' do
56
+ redis.zrange('my_store:~', 0, -1, with_scores: true).should == [["8", 8], ["17", 17]]
57
+ end
58
+
59
+ it 'should store range values' do
60
+ redis.keys.should =~ ["my_store:~", "my_store:8", "my_store:17"]
61
+ state.should == { 8 => "A", 17 => "A" }
62
+ end
63
+
64
+ it 'should return OK if successful' do
65
+ add("B", 9, 18).should == "OK"
66
+ end
67
+
68
+ end
69
+
70
+ describe "merging" do
71
+ before { add("A", 8, 17) }
72
+
73
+ it 'should add non overlapping, left (B B A A)' do
74
+ lambda { add("B", 5, 6) }.should change { state }.
75
+ to 5 => "B", 6 => "B", 8 => "A", 17 => "A"
76
+ end
77
+
78
+ it 'should add non overlapping, right (A A B B)' do
79
+ lambda { add("B", 18, 23) }.should change { state }.
80
+ to 8 => "A", 17 => "A", 18 => "B", 23 => "B"
81
+ end
82
+
83
+ it 'should add non overlapping, inner (A B B A)' do
84
+ lambda { add("B", 9, 12) }.should change { state }.
85
+ to 8 => "A", 9 => "AB", 12 => "AB", 17 => "A"
86
+ end
87
+
88
+ it 'should add overlapping, left (B A B A)' do
89
+ lambda { add("B", 6, 10) }.should change { state }.
90
+ to 6 => "B", 8 => "AB", 10 => "AB", 17 => "A"
91
+ end
92
+
93
+ it 'should add overlapping, right (A B A B)' do
94
+ lambda { add("B", 15, 23) }.should change { state }.
95
+ to 8 => "A", 15 => "AB", 17 => "AB", 23 => "B"
96
+ end
97
+
98
+ it 'should add overlapping, both (AB BA)' do
99
+ lambda { add("B", 8, 17) }.should change { state }.
100
+ to 8 => "AB", 17 => "AB"
101
+ end
102
+
103
+ it 'should add tangent, outer left (B BA A)' do
104
+ lambda { add("B", 6, 8) }.should change { state }.
105
+ to 6 => "B", 8 => "AB", 17 => "A"
106
+ end
107
+
108
+ it 'should add tangent, inner left (AB B A)' do
109
+ lambda { add("B", 8, 10) }.should change { state }.
110
+ to 8 => "AB", 10 => "AB", 17 => "A"
111
+ end
112
+
113
+ it 'should add tangent, outer right (A AB B)' do
114
+ lambda { add("B", 17, 22) }.should change { state }.
115
+ to 8 => "A", 17 => "AB", 22 => "B"
116
+ end
117
+
118
+ it 'should add tangent, inner right (A B BA)' do
119
+ lambda { add("B", 12, 17) }.should change { state }.
120
+ to 8 => "A", 12 => "AB", 17 => "AB"
121
+ end
122
+
123
+ end
124
+
125
+ it 'should bring it all together' do
126
+ add("A", 8, 17)
127
+ add("B", 5, 6)
128
+ add("C", 18, 23)
129
+ add("D", 9, 12)
130
+ add("E", 6, 10)
131
+ add("F", 15, 23)
132
+ add("G", 8, 17)
133
+ add("H", 6, 8)
134
+ add("I", 8, 10)
135
+ add("J", 7, 22)
136
+ add("K", 12, 17)
137
+
138
+ state.should == {
139
+ 5 => "B", 6 => "BEH", 7 => "EHJ",
140
+ 8 => "AEGHIJ", 9 => "ADEGIJ", 10 => "ADEGIJ",
141
+ 12 => "ADGJK", 15 => "AFGJK", 17 => "AFGJK",
142
+ 18 => "CFJ", 22 => "CFJ", 23 => "CF"
143
+ }
144
+ end
145
+
146
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Range Lookup: Lookup" do
4
+
5
+ def add(member, min, max)
6
+ evalsha :add, keys: ["my_store"], argv: [member, min, max]
7
+ end
8
+
9
+ def lookup(value)
10
+ evalsha :lookup, keys: ["my_store"], argv: [value]
11
+ end
12
+
13
+ before do
14
+ add("A", 0, 8)
15
+ add("B", 4, 6)
16
+ add("C", 2, 9)
17
+ add("D", 7, 10)
18
+ end
19
+
20
+ def state
21
+ ranges = {}
22
+ redis.zrange('my_store:~', 0, -1, with_scores: true).each do |value, score|
23
+ ranges[score.to_f] = redis.smembers("my_store:#{value}").sort
24
+ end
25
+ ranges
26
+ end
27
+
28
+ it 'should catch invalid arguments' do
29
+ lambda { evalsha :lookup }.should raise_error(Redis::CommandError, /wrong number of arguments/)
30
+ lambda { evalsha :lookup, keys: ["one"] }.should raise_error(Redis::CommandError, /wrong number of arguments/)
31
+ lambda { evalsha :lookup, keys: ["one"], argv: ["one", 2] }.should raise_error(Redis::CommandError, /wrong number of arguments/)
32
+ end
33
+
34
+ it 'should catch non-numeric ranges' do
35
+ lambda { evalsha :lookup, keys: ["one"], argv: ["one"] }.should raise_error(Redis::CommandError, /not numeric or out of range/)
36
+ end
37
+
38
+ it 'should return the members when found' do
39
+ lookup(0).should =~ ["A"]
40
+ lookup(1).should =~ ["A"]
41
+ lookup(2).should =~ ["A", "C"]
42
+ lookup(3).should =~ ["A", "C"]
43
+ lookup(4).should =~ ["A", "B", "C"]
44
+ lookup(5).should =~ ["A", "B", "C"]
45
+ lookup(6).should =~ ["A", "B", "C"]
46
+ lookup(7).should =~ ["A", "C", "D"]
47
+ lookup(8).should =~ ["A", "C", "D"]
48
+ lookup(9).should =~ ["C", "D"]
49
+ lookup(10).should =~ ["D"]
50
+ end
51
+
52
+ it 'should return an empty set if not found' do
53
+ lookup(-1).should =~ []
54
+ lookup(11).should =~ []
55
+ lookup(111).should =~ []
56
+ end
57
+
58
+ it 'should support float lookups' do
59
+ lookup(6.5).should =~ ["A", "C"]
60
+ lookup(7.5).should =~ ["A", "C", "D"]
61
+ lookup(10.1).should =~ []
62
+ end
63
+
64
+ end
@@ -0,0 +1,189 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Range Lookup: Removing" do
4
+
5
+ def state(c = :to_i)
6
+ ranges = {}
7
+ redis.zrange('my_store:~', 0, -1, with_scores: true).each do |value, score|
8
+ ranges[score.send(c)] = redis.smembers("my_store:#{value}").sort.join
9
+ end
10
+ ranges
11
+ end
12
+
13
+ def brackets
14
+ redis.zrange('my_store:~', 0, -1)
15
+ end
16
+
17
+ def add(member, min, max)
18
+ evalsha :add, keys: ["my_store"], argv: [member, min, max]
19
+ end
20
+
21
+ def remove(member, min, max)
22
+ evalsha :remove, keys: ["my_store"], argv: [member, min, max]
23
+ end
24
+
25
+ def scenario(pairs)
26
+ pairs.each do |member, range|
27
+ add member.to_s, range.first, range.last
28
+ end
29
+ end
30
+
31
+ describe "general" do
32
+
33
+ it 'should catch invalid arguments' do
34
+ lambda { evalsha :remove }.should raise_error(Redis::CommandError, /wrong number of arguments/)
35
+ lambda { evalsha :remove, keys: ["one"] }.should raise_error(Redis::CommandError, /wrong number of arguments/)
36
+ lambda { evalsha :remove, keys: ["one"], argv: ["one", 2] }.should raise_error(Redis::CommandError, /wrong number of arguments/)
37
+ lambda { evalsha :remove, keys: ["one", "two"], argv: ["one", 2, 3] }.should raise_error(Redis::CommandError, /wrong number of arguments/)
38
+ end
39
+
40
+ it 'should catch non-numeric ranges' do
41
+ lambda { evalsha :remove, keys: ["one"], argv: ["one", "two", 3] }.should raise_error(Redis::CommandError, /not numeric or out of range/)
42
+ lambda { evalsha :remove, keys: ["one"], argv: ["one", 2, "three"] }.should raise_error(Redis::CommandError, /not numeric or out of range/)
43
+ lambda { evalsha :remove, keys: ["one"], argv: ["one", 3, 2] }.should raise_error(Redis::CommandError, /not numeric or out of range/)
44
+ end
45
+
46
+ it 'should return OK if successful' do
47
+ remove("A", 8, 17).should == "OK"
48
+ end
49
+
50
+ end
51
+
52
+ describe "removing" do
53
+ before { add("A", 8, 17) }
54
+
55
+ it 'should support zero bounds' do
56
+ scenario B: -5..0, C: 0..5
57
+
58
+ lambda {
59
+ remove("B", -5, 0)
60
+ remove("C", 0, 5)
61
+ }.should change { state }.
62
+ from(-5 => "B", 0 => "BC", 5 => "C", 8 => "A", 17 => "A").
63
+ to(8 => "A", 17 => "A")
64
+ end
65
+
66
+ it 'should clean up index' do
67
+ lambda {
68
+ add("B", 15, 22)
69
+ add("C", 15, 23)
70
+ }.should change { brackets }.to ["8", "15", "17", "22", "23"]
71
+
72
+ lambda {
73
+ remove("B", 15, 22)
74
+ }.should change { brackets }.to ["8", "15", "17", "23"]
75
+
76
+ lambda {
77
+ remove("C", 15, 23)
78
+ }.should change { brackets }.to ["8", "17"]
79
+
80
+ lambda {
81
+ remove("A", 8, 17)
82
+ }.should change { brackets }.to []
83
+ end
84
+
85
+ end
86
+
87
+ describe "use cases" do
88
+
89
+ it "should remove from [A: 1-2, B: 3-4]" do
90
+ scenario A: 1..2, B: 3..4
91
+ lambda { remove("A", 1, 2) }.should change { state }.
92
+ from(1 => "A", 2 => "A", 3 => "B", 4 => "B").
93
+ to(3 => "B", 4 => "B")
94
+ end
95
+
96
+ it "should remove from [A: 3-4, B: 1-2]" do
97
+ scenario A: 3..4, B: 1..2
98
+ lambda { remove("A", 3, 4) }.should change { state }.
99
+ from(1 => "B", 2 => "B", 3 => "A", 4 => "A").
100
+ to(1 => "B", 2 => "B")
101
+ end
102
+
103
+ it "should remove from [A: 1-3, B: 2-4]" do
104
+ scenario A: 1..3, B: 2..4
105
+ lambda { remove("A", 1, 3) }.should change { state }.
106
+ from(1 => "A", 2 => "AB", 3 => "AB", 4 => "B").
107
+ to(2 => "B", 4 => "B")
108
+ end
109
+
110
+ it "should remove from [A: 2-4, B: 1-3]" do
111
+ scenario A: 2..4, B: 1..3
112
+ lambda { remove("A", 2, 4) }.should change { state }.
113
+ from(1 => "B", 2 => "AB", 3 => "AB", 4 => "A").
114
+ to(1 => "B", 3 => "B")
115
+ end
116
+
117
+ it "should remove from [A: 1-4, B: 2-3]" do
118
+ scenario A: 1..4, B: 2..3
119
+ lambda { remove("A", 1, 4) }.should change { state }.
120
+ from(1 => "A", 2 => "AB", 3 => "AB", 4 => "A").
121
+ to(2 => "B", 3 => "B")
122
+ end
123
+
124
+ it "should remove from [A: 2-3, B: 1-4]" do
125
+ scenario A: 2..3, B: 1..4
126
+ lambda { remove("A", 2, 3) }.should change { state }.
127
+ from(1 => "B", 2 => "AB", 3 => "AB", 4 => "B").
128
+ to(1 => "B", 4 => "B")
129
+ end
130
+
131
+ it "should remove from [A: 1-3, B: 3-4]" do
132
+ scenario A: 1..3, B: 3..4
133
+ lambda { remove("A", 1, 3) }.should change { state }.
134
+ from(1 => "A", 3 => "AB", 4 => "B").
135
+ to(3 => "B", 4 => "B")
136
+ end
137
+
138
+ it "should remove from [A: 1-4, B: 3-4]" do
139
+ scenario A: 1..4, B: 3..4
140
+ lambda { remove("A", 1, 4) }.should change { state }.
141
+ from(1 => "A", 3 => "AB", 4 => "AB").
142
+ to(3 => "B", 4 => "B")
143
+ end
144
+
145
+ it "should remove from [A: 3-4, B: 1-3]" do
146
+ scenario A: 3..4, B: 1..3
147
+ lambda { remove("A", 3, 4) }.should change { state }.
148
+ from(1 => "B", 3 => "AB", 4 => "A").
149
+ to(1 => "B", 3 => "B")
150
+ end
151
+
152
+ it "should remove from [A: 3-4, B: 1-4]" do
153
+ scenario A: 3..4, B: 1..4
154
+ lambda { remove("A", 3, 4) }.should change { state }.
155
+ from(1 => "B", 3 => "AB", 4 => "AB").
156
+ to(1 => "B", 4 => "B")
157
+ end
158
+
159
+ it "should remove from [A: 1-4, B: 1-2]" do
160
+ scenario A: 1..4, B: 1..2
161
+ lambda { remove("A", 1, 4) }.should change { state }.
162
+ from(1 => "AB", 2 => "AB", 4 => "A").
163
+ to(1 => "B", 2 => "B")
164
+ end
165
+
166
+ it "should remove from [A: 2-4, B: 1-2]" do
167
+ scenario A: 2..4, B: 1..2
168
+ lambda { remove("A", 2, 4) }.should change { state }.
169
+ from(1 => "B", 2 => "AB", 4 => "A").
170
+ to(1 => "B", 2 => "B")
171
+ end
172
+
173
+ it "should remove from [A: 1-4, B: 1-4]" do
174
+ scenario A: 1..4, B: 1..4
175
+ lambda { remove("A", 1, 4) }.should change { state }.
176
+ from(1 => "AB", 4 => "AB").
177
+ to(1 => "B", 4 => "B")
178
+ end
179
+
180
+ it "should remove from [A: 1-4]" do
181
+ scenario A: 1..4
182
+ lambda { remove("A", 1, 4) }.should change { state }.
183
+ from(1 => "A", 4 => "A").
184
+ to({})
185
+ end
186
+
187
+ end
188
+
189
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Range Lookup X: Adding" do
4
+
5
+ def state(c = :to_i)
6
+ ranges = {}
7
+ redis.zrange('my_store:~', 0, -1, with_scores: true).each do |value, score|
8
+ ranges[score.send(c)] = redis.smembers("my_store:#{value}").sort.join
9
+ end
10
+ ranges
11
+ end
12
+
13
+ def add(member, min, max)
14
+ evalsha :add, keys: ["my_store"], argv: [member, min, max]
15
+ end
16
+
17
+ describe "general" do
18
+
19
+ it 'should catch invalid arguments' do
20
+ lambda { evalsha :add }.should raise_error(Redis::CommandError, /wrong number of arguments/)
21
+ lambda { evalsha :add, keys: ["one"] }.should raise_error(Redis::CommandError, /wrong number of arguments/)
22
+ lambda { evalsha :add, keys: ["one"], argv: ["one", 2] }.should raise_error(Redis::CommandError, /wrong number of arguments/)
23
+ lambda { evalsha :add, keys: ["one", "two"], argv: ["one", 2, 3] }.should raise_error(Redis::CommandError, /wrong number of arguments/)
24
+ end
25
+
26
+ it 'should catch non-numeric ranges' do
27
+ lambda { evalsha :add, keys: ["one"], argv: ["one", "two", 3] }.should raise_error(Redis::CommandError, /not numeric or out of range/)
28
+ lambda { evalsha :add, keys: ["one"], argv: ["one", 2, "three"] }.should raise_error(Redis::CommandError, /not numeric or out of range/)
29
+ lambda { evalsha :add, keys: ["one"], argv: ["one", 3, 2] }.should raise_error(Redis::CommandError, /not numeric or out of range/)
30
+ end
31
+
32
+ it 'should NOT support single-value ranges' do
33
+ lambda { add("B", 10, 10) }.should raise_error(Redis::CommandError, /not numeric or out of range/)
34
+ end
35
+
36
+ end
37
+
38
+ describe "adding" do
39
+
40
+ it 'should store ranges' do
41
+ lambda { add("A", 8, 17) }.should change { state }.
42
+ to 8 => "A", 17 => ""
43
+ end
44
+
45
+ it 'should support float/decimal ranges' do
46
+ lambda { add("A", 7.5, 17.9); add("B", 8.1, 10.6) }.should change { state(:to_f) }.
47
+ to 7.5 => "A", 8.1 => "AB", 10.6 => "A", 17.9 => ""
48
+ end
49
+
50
+ it 'should support nagative ranges and bounds' do
51
+ lambda { add("B", 0, 5) }.should change { state }.
52
+ to 0 => "B", 5 => ""
53
+
54
+ lambda { add("C", -5, 0) }.should change { state }.
55
+ to -5 => "C", 0 => "B", 5 => ""
56
+ end
57
+
58
+ it 'should maintain an index' do
59
+ lambda { add("A", 8, 17) }.should change {
60
+ redis.zrange('my_store:~', 0, -1, with_scores: true)
61
+ }.to [["8", 8.0], ["17", 17.0]]
62
+ end
63
+
64
+ it 'should store range members' do
65
+ lambda { add("A", 8, 17) }.should change { redis.keys.sort }.
66
+ to ["my_store:8", "my_store:~"]
67
+ end
68
+
69
+ it 'should return OK if successful' do
70
+ add("A", 8, 17).should == "OK"
71
+ end
72
+
73
+ end
74
+
75
+ describe "merging" do
76
+ before { add("A", 8, 17) }
77
+
78
+ it 'should add non overlapping, left (B B A A)' do
79
+ lambda { add("B", 5, 6) }.should change { state }.
80
+ to 5 => "B", 6 => "", 8 => "A", 17 => ""
81
+ end
82
+
83
+ it 'should add non overlapping, right (A A B B)' do
84
+ lambda { add("B", 18, 23) }.should change { state }.
85
+ to 8 => "A", 17 => "", 18 => "B", 23 => ""
86
+ end
87
+
88
+ it 'should add non overlapping, inner (A B B A)' do
89
+ lambda { add("B", 9, 12) }.should change { state }.
90
+ to 8 => "A", 9 => "AB", 12 => "A", 17 => ""
91
+ end
92
+
93
+ it 'should add overlapping, left (B A B A)' do
94
+ lambda { add("B", 6, 10) }.should change { state }.
95
+ to 6 => "B", 8 => "AB", 10 => "A", 17 => ""
96
+ end
97
+
98
+ it 'should add overlapping, right (A B A B)' do
99
+ lambda { add("B", 15, 23) }.should change { state }.
100
+ to 8 => "A", 15 => "AB", 17 => "B", 23 => ""
101
+ end
102
+
103
+ it 'should add overlapping, both (AB BA)' do
104
+ lambda { add("B", 8, 17) }.should change { state }.
105
+ to 8 => "AB", 17 => ""
106
+ end
107
+
108
+ it 'should add tangent, outer left (B BA A)' do
109
+ lambda { add("B", 6, 8) }.should change { state }.
110
+ to 6 => "B", 8 => "A", 17 => ""
111
+ end
112
+
113
+ it 'should add tangent, inner left (AB B A)' do
114
+ lambda { add("B", 8, 10) }.should change { state }.
115
+ to 8 => "AB", 10 => "A", 17 => ""
116
+ end
117
+
118
+ it 'should add tangent, outer right (A AB B)' do
119
+ lambda { add("B", 17, 22) }.should change { state }.
120
+ to 8 => "A", 17 => "B", 22 => ""
121
+ end
122
+
123
+ it 'should add tangent, inner right (A B BA)' do
124
+ lambda { add("B", 12, 17) }.should change { state }.
125
+ to 8 => "A", 12 => "AB", 17 => ""
126
+ end
127
+
128
+ it 'should work with multiple ranges' do
129
+ add("B", 5, 6)
130
+ add("C", 18, 23)
131
+ add("D", 9, 12)
132
+ add("E", 6, 10)
133
+ add("F", 15, 23)
134
+ add("G", 8, 17)
135
+ add("H", 6, 8)
136
+ add("I", 8, 10)
137
+ add("J", 7, 22)
138
+ add("K", 12, 17)
139
+
140
+ state.should == {
141
+ 5 => "B", 6 => "EH", 7 => "EHJ",
142
+ 8 => "AEGIJ", 9 => "ADEGIJ", 10 => "ADGJ",
143
+ 12 => "AGJK", 15 => "AFGJK", 17 => "FJ",
144
+ 18 => "CFJ", 22 => "CF", 23 => ""
145
+ }
146
+ end
147
+ end
148
+
149
+ end