suggestor 0.0.6 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -18,16 +18,19 @@ In the example, the user "Alvaro Pereyra Rabanal" has seen movies "Primer" and "
18
18
 
19
19
  After loading the gem with the data:
20
20
 
21
- engine = Suggestor::Engine.new(data)
21
+ suggestor = Suggestor::Suggestor.new(data)
22
22
 
23
23
  We can start to get some results.
24
24
 
25
+ If using Ruby, you can also send Suggestor just a hash object and it'll take it aswell:
26
+
27
+ suggestor = Suggestor::Suggestor.new(JSON.parse(data))
25
28
 
26
29
  ### Similar items
27
30
 
28
31
  For example, we can get similar users:
29
32
 
30
- engine.similar_to("Alvaro Pereyra Rabanal")
33
+ suggestor.similar_to("Alvaro Pereyra Rabanal")
31
34
 
32
35
  Which will return an structure like
33
36
 
@@ -41,15 +44,15 @@ Thus, you can load the data and save their similarity scores for later use.
41
44
 
42
45
  You can limit the data passing a "size" argument:
43
46
 
44
- engine.similar_to("Alvaro Pereyra Rabanal", :size => 5)
47
+ suggestor.similar_to("Alvaro Pereyra Rabanal", :size => 5)
45
48
 
46
49
  Now, that fine and all, but what about Mr. Bob who always is ranking everything
47
50
  higher. ID4 maybe is not that good after all. If that happens, Suggestor allows you to change the algorithm used:
48
51
 
49
52
  algorithm = Suggestor::Algorithms::PearsonCorrelation
50
- engine = Suggestor::Engine.new(data, algorithm)
53
+ suggestor = Suggestor::Suggestor.new(data, algorithm)
51
54
 
52
- engine.recommended_to("Alvaro Pereyra Rabanal")
55
+ suggestor.recommended_to("Alvaro Pereyra Rabanal")
53
56
 
54
57
  There are two implemented methods, Euclidean Distance and Pearson Correlation.
55
58
 
@@ -64,7 +67,7 @@ take in mind if some user grades higher or lower and return more exact suggestio
64
67
  Most interestingly, the gem allows you to get suggestions base on the data.
65
68
  For example, which movies shoud user "2" watch based on his reviews, and similar other users tastes?
66
69
 
67
- engine.recommended_to("Alvaro Pereyra Rabanal")
70
+ suggestor.recommended_to("Alvaro Pereyra Rabanal")
68
71
 
69
72
  As before, the structure returned will be
70
73
 
@@ -79,6 +82,21 @@ We can also invert the data that the user has added, enableing us to get
79
82
  similar related items. For example, let's say I'm on a Movie profile and
80
83
  want to check which other movies are similar to it:
81
84
 
82
- engine.similar_related_to("Batman Begins ", :size => 5)
85
+ suggestor.similar_related_to("Batman Begins ", :size => 5)
86
+
87
+ ### Suggested items for a set
88
+
89
+ Say that you have the list of the movies that you liked the most and you'd like to know what you should watch next. Suggestor has you covered:
90
+
91
+ suggestor.items_for_set ['Batman Begins', 'Cyrus']
92
+ # => [["Kung Fu Hustle", 0.5], ["Super 8 ", 0.5], ["Celda 211 ", 0.5], ... ]
93
+
94
+ Suggestor of course considers the whole set when recommending new items, so different sets will yield different results, even if they differ in only one item. Say you picked Super 8 from the previous query:
95
+ suggestor.items_for_set ['Batman Begins', 'Cyrus', 'Super 8 ']
96
+ # => [["Rapidos y Furiosos 5", 0.3333333333333333], ["Agente Salt", 0.3333333333333333], ["Mary and Max ", 0.3333333333333333], ... ]
97
+
98
+ But if you had picked Kung Fu Hustle:
99
+ suggestor.items_for_set ['Batman Begins', 'Cyrus', 'Kung Fu Hustle']
100
+ # => [["El mensajero ", 0.6666666666666666], ["El Club de la Pelea", 0.3333333333333333], ["Un tonto en el amor", 0.3333333333333333], ... ]
83
101
 
84
102
  Now you can go and build your awesome recommendations web site :)
@@ -4,12 +4,12 @@ require_relative '../lib/suggestor'
4
4
  # Each user have a hash of their reviews with the movie and
5
5
  # what they've rate them with
6
6
  json = File.read("test/movies.json")
7
- engine = Suggestor::Engine.new(json, Suggestor::Algorithms::EuclideanDistance)
7
+ suggestor = Suggestor::Suggestor.new(json)
8
8
 
9
9
  # Let's get some similar users
10
10
  name = "Alvaro Pereyra Rabanal"
11
11
  puts "Who is similar to #{name}"
12
- puts engine.similar_to(name, size: 5).inspect
12
+ puts suggestor.similar_to(name, size: 5).inspect
13
13
 
14
14
  puts
15
15
  puts
@@ -17,7 +17,7 @@ puts
17
17
  # So, after knowing them, why not having some recommendations?
18
18
  puts "Interesting! But I want to see some stuff at the movies, what to watch?"
19
19
  opts = {size: 5}
20
- results = engine.recommended_to("Alvaro Pereyra Rabanal", opts)
20
+ results = suggestor.recommended_to("Alvaro Pereyra Rabanal", opts)
21
21
 
22
22
  puts results.inspect
23
23
 
@@ -26,17 +26,29 @@ puts
26
26
 
27
27
  # That's good, but let's take in mind bias while using Pearson Correlation:
28
28
  puts "Adjust this results please"
29
- engine = Suggestor::Engine.new(json,Suggestor::Algorithms::PearsonCorrelation)
29
+ suggestor = Suggestor::Suggestor.new(json,Suggestor::Algorithms::PearsonCorrelation)
30
30
 
31
31
  ops = {size: 5}
32
- results = engine.recommended_to("Alvaro Pereyra Rabanal", opts)
32
+ results = suggestor.recommended_to("Alvaro Pereyra Rabanal", opts)
33
33
  puts results.inspect
34
34
 
35
35
  puts
36
36
  puts
37
37
 
38
+ puts "Adjust this results please"
39
+ suggestor = Suggestor::Suggestor.new(json)
40
+
38
41
  name = "Batman Begins "
39
42
  puts "Now that was nice. But which others are similar to '#{name}'"
40
43
  ops = {size: 10}
41
- results = engine.similar_related_to(name, opts)
44
+ results = suggestor.similar_related_to(name, opts)
45
+ puts results.inspect
46
+
47
+ puts
48
+ puts
49
+
50
+ set = ['Batman Begins', 'Cyrus']
51
+ puts "Great! One more thing: if I've watched all these movies: #{set.inspect},
52
+ which should I watch next?"
53
+ results = suggestor.items_for_set set
42
54
  puts results.inspect
@@ -1,5 +1,47 @@
1
- require_relative 'suggestor/engine'
1
+ require_relative 'suggestor/algorithms/records'
2
2
  require_relative 'suggestor/algorithms/recommendation_algorithm'
3
3
  require_relative 'suggestor/algorithms/euclidean_distance'
4
4
  require_relative 'suggestor/algorithms/pearson_correlation'
5
5
 
6
+ require 'json'
7
+ module Suggestor
8
+ class WrongInputFormat < Exception; end
9
+
10
+ class Suggestor
11
+
12
+ def initialize(input, algorithm = Algorithms::EuclideanDistance)
13
+ collection = load_from(input)
14
+ @algorithm = algorithm.new(collection)
15
+ end
16
+
17
+ def similar_to(item, opts={})
18
+ @algorithm.similar_to(item, opts)
19
+ end
20
+
21
+ def recommended_to(item, opts={})
22
+ @algorithm.recommended_to(item, opts)
23
+ end
24
+
25
+ def similar_related_to(item, opts={})
26
+ @algorithm.similar_related_to(item, opts)
27
+ end
28
+
29
+ def items_for_set(set, opts={})
30
+ @algorithm.items_for_set(set, opts)
31
+ end
32
+
33
+ private
34
+
35
+ def load_from(input)
36
+ return input if input.is_a? Hash
37
+ parse_from_json(input)
38
+ end
39
+
40
+ def parse_from_json(json)
41
+ JSON.parse(json)
42
+ rescue Exception => ex
43
+ raise WrongInputFormat, "Wrong Data format: #{ex.message}"
44
+ end
45
+
46
+ end
47
+ end
@@ -23,18 +23,8 @@ module Suggestor
23
23
  include RecommendationAlgorithm
24
24
 
25
25
  def similarity_score(first, second)
26
- return 0.0 if nothing_shared?(first, second)
27
- inverse_of_squares(first, second)
28
- end
29
-
30
- def inverse_of_squares(first, second)
31
- 1/(1+Math.sqrt(sum_squares(first, second)))
32
- end
33
-
34
- def sum_squares(first, second)
35
- shared_items(first, second).inject(0.0) do |sum, item|
36
- sum + ( values_for(first)[item] - values_for(second)[item] ) ** 2
37
- end
26
+ return 0.0 if @collection.nothing_shared?(first, second)
27
+ @collection.inverse_of_squares(first, second)
38
28
  end
39
29
 
40
30
  end
@@ -34,11 +34,12 @@ module Suggestor
34
34
  include RecommendationAlgorithm
35
35
 
36
36
  def similarity_score(first, second)
37
- return -1.0 if nothing_shared?(first, second)
37
+ return -1.0 if @collection.nothing_shared?(first, second)
38
38
 
39
39
  process_values(first, second)
40
40
 
41
41
  numerator = difference_from_values
42
+
42
43
  denominator = square_root_from_differences
43
44
 
44
45
  return 0.0 if denominator == 0
@@ -48,11 +49,11 @@ module Suggestor
48
49
  private
49
50
 
50
51
  def process_values(first, second)
51
- items = shared_items(first, second)
52
+ items = @collection.shared_items(first, second)
52
53
  @total_related_items = items.size.to_f
53
54
 
54
- first_values = values_for(first)
55
- second_values = values_for(second)
55
+ first_values = @collection.values_for(first)
56
+ second_values = @collection.values_for(second)
56
57
 
57
58
  create_helper_variables
58
59
 
@@ -5,15 +5,16 @@ module Suggestor
5
5
  attr_accessor :collection
6
6
 
7
7
  def initialize(collection)
8
- @collection = collection
8
+ @collection = Records.new(collection)
9
9
  end
10
10
 
11
11
  # Ex. Similar users based on their movies reviews
12
12
  def similar_to(main, opts={})
13
13
  opts.merge!(default_options)
14
-
15
- collection = remove_self(main)
16
- results = order_by_similarity_score(main,collection)
14
+
15
+ cleaned = @collection.remove(main)
16
+
17
+ results = order_by_similarity_score(main, cleaned)
17
18
 
18
19
  sort_results(results,opts[:size])
19
20
  end
@@ -25,6 +26,7 @@ module Suggestor
25
26
  @similarities = @totals = Hash.new(0)
26
27
 
27
28
  create_similarities_totals(main)
29
+
28
30
  results = generate_rankings
29
31
 
30
32
  sort_results(results,opts[:size])
@@ -34,72 +36,43 @@ module Suggestor
34
36
  def similar_related_to(main, opts={})
35
37
  opts.merge!(default_options)
36
38
 
37
- collection = invert_collection
38
- engine = self.class.new(collection)
39
+ inverted_collection = @collection.invert
40
+ suggestor = self.class.new(inverted_collection)
39
41
 
40
- engine.similar_to(main,opts)
42
+ suggestor.similar_to(main,opts)
41
43
  end
42
44
 
43
- def shared_items(first, second)
44
- return [] unless values_for(first) && values_for(second)
45
-
46
- related_keys_for(first).select do |item|
47
- related_keys_for(second).include? item
48
- end
49
- end
50
-
51
- private
45
+ # Takes a set and returns suggestions based on the weighted average of
46
+ # the similarities to all of the items in it.
47
+ def items_for_set(set, opts={})
48
+ opts.merge!(default_options)
52
49
 
53
- def default_options
54
- {size: 5}
55
- end
50
+ suggestions = Hash.new(0.0)
56
51
 
57
- def nothing_shared?(first, second)
58
- shared_items(first, second).empty?
59
- end
52
+ set.each do |item|
53
+ related = similar_related_to(item, opts)
54
+ related.each { |rel| suggestions[rel[0]] += rel[1] unless
55
+ set.include? rel[0] }
56
+ end
60
57
 
61
- def remove_self(main)
62
- cleaned = collection.dup
63
- cleaned.delete(main)
64
- cleaned
58
+ suggestions = suggestions.map { |label, score| [label, score / set.length] }
59
+ suggestions.sort_by { |sug| sug[1] }.reverse
65
60
  end
66
61
 
62
+ private
67
63
 
68
- # changes { "Cat": {"1": 10, "2":20}, "Dog": {"1":5, "2": 15} }
69
- # to {"1": {"Cat": 10, "Dog": 5}, "2": {"Cat": 20, "Dog": 15}
70
- def invert_collection
71
- results = {}
72
-
73
- collection.keys.each do |main|
74
- collection[main].keys.each do |item|
75
- results[item] ||= {}
76
- results[item][main] = collection[main][item]
77
- end
78
- end
79
-
80
- results
64
+ def default_options
65
+ {size: 5}
81
66
  end
82
67
 
83
- def order_by_similarity_score(main,collection)
68
+ def order_by_similarity_score(main, collection)
84
69
  result = collection.keys.inject({}) do |res, other|
85
70
  res.merge!({other => similarity_score(main, other)})
86
71
  end
87
72
  end
88
-
89
- def already_has?(main, related)
90
- collection[main].has_key?(related)
91
- end
92
73
 
93
- def values_for(id)
94
- collection[id.to_s]
95
- end
96
-
97
- def related_keys_for(id)
98
- values_for(id).keys
99
- end
100
-
101
74
  def add_to_totals(other, item, score)
102
- @totals[item] += collection[other][item]*score
75
+ @totals[item] += @collection[other][item] * score
103
76
  @similarities[item] += score
104
77
  end
105
78
 
@@ -123,23 +96,19 @@ module Suggestor
123
96
  score > 0
124
97
  end
125
98
 
126
- def same_item?(main, other)
127
- other == main
128
- end
129
-
130
99
  def create_similarities_totals(main)
131
100
 
132
- collection.keys.each do |other|
101
+ @collection.keys.each do |other|
133
102
 
134
- next if same_item?(main,other)
103
+ next if other == main
135
104
 
136
105
  score = similarity_score(main, other)
137
106
 
138
107
  next unless something_in_common?(score)
139
108
 
140
- collection[other].keys.each do |item|
109
+ @collection[other].keys.each do |item|
141
110
 
142
- unless already_has?(main, item)
111
+ unless @collection.already_has?(main, item)
143
112
  add_to_totals(other, item, score)
144
113
  end
145
114
 
@@ -152,4 +121,4 @@ module Suggestor
152
121
 
153
122
  end
154
123
  end
155
- end
124
+ end
@@ -0,0 +1,76 @@
1
+ require 'delegate'
2
+
3
+ module Suggestor
4
+ module Algorithms
5
+ class Records < DelegateClass(Hash)
6
+
7
+ def initialize(hash)
8
+ super(hash)
9
+ end
10
+
11
+ def dup
12
+ Marshal.load(Marshal.dump(self))
13
+ end
14
+
15
+ def remove(item)
16
+ cleaned = self.dup
17
+ cleaned.delete(item)
18
+ cleaned
19
+ end
20
+
21
+ def common_items(first, other)
22
+ values_for(first) && values_for(other)
23
+ end
24
+
25
+ def already_has?(main, related)
26
+ self[main].has_key?(related)
27
+ end
28
+
29
+ def values_for(id)
30
+ self[id.to_s]
31
+ end
32
+
33
+ def related_keys_for(id)
34
+ values_for(id).keys
35
+ end
36
+
37
+ def shared_items(first, other)
38
+ return [] unless common_items(first, other)
39
+
40
+ related_keys_for(first).select do |item|
41
+ related_keys_for(other).include? item
42
+ end
43
+ end
44
+
45
+ def nothing_shared?(first, second)
46
+ shared_items(first, second).empty?
47
+ end
48
+
49
+ def inverse_of_squares(first, second)
50
+ 1/(1+Math.sqrt(sum_squares(first, second)))
51
+ end
52
+
53
+ def sum_squares(first, second)
54
+ shared_items(first, second).inject(0.0) do |sum, item|
55
+ sum + ( values_for(first)[item] - values_for(second)[item] ) ** 2
56
+ end
57
+ end
58
+
59
+ # changes { "Cat": {"1": 10, "2":20}, "Dog": {"1":5, "2": 15} }
60
+ # to {"1": {"Cat": 10, "Dog": 5}, "2": {"Cat": 20, "Dog": 15}
61
+ def invert
62
+ results = {}
63
+
64
+ self.keys.each do |main|
65
+ self[main].keys.each do |item|
66
+ results[item] ||= {}
67
+ results[item][main] = self[main][item]
68
+ end
69
+ end
70
+
71
+ results
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -1,3 +1,3 @@
1
1
  module Suggestor
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -1,21 +1,37 @@
1
1
  require 'minitest/autorun'
2
2
  require_relative '../lib/suggestor'
3
3
 
4
- describe Suggestor::Engine do
4
+ describe Suggestor::Suggestor do
5
5
  before do
6
6
  @data_string = File.read("test/numbers.json")
7
7
  end
8
8
 
9
9
  describe "when loading up the data structure" do
10
10
  it "must raise an exception with invalid data" do
11
- lambda{ Suggestor::Engine.new("GIBBERISH") }.must_raise Suggestor::WrongInputFormat
11
+ lambda{ Suggestor::Suggestor.new("GIBBERISH") }.must_raise Suggestor::WrongInputFormat
12
12
  end
13
13
  end
14
14
 
15
+ describe "when using a hash object for loading" do
16
+
17
+ before do
18
+ @data_string = File.read("test/numbers.json")
19
+ @data = JSON.parse(@data_string)
20
+ @suggestor = Suggestor::Suggestor.new(@data)
21
+ end
22
+
23
+ it "must return similar items from the base one with euclidean distance" do
24
+ expected = [["3", 0.14285714285714285], ["2", 0.14285714285714285]]
25
+ @suggestor.similar_to("1").must_be :==, expected
26
+ end
27
+
28
+
29
+ end
30
+
15
31
  describe "when accesing the data after load_dataing it" do
16
32
 
17
33
  before do
18
- @suggestor = Suggestor::Engine.new(@data_string)
34
+ @suggestor = Suggestor::Suggestor.new(@data_string)
19
35
  end
20
36
 
21
37
  it "must return similar items from the base one with euclidean distance" do
@@ -24,7 +40,7 @@ require_relative '../lib/suggestor'
24
40
  end
25
41
 
26
42
  it "must return similar items from the base one with pearson correlation" do
27
- @suggestor = Suggestor::Engine.new(@data_string,Suggestor::Algorithms::PearsonCorrelation)
43
+ @suggestor = Suggestor::Suggestor.new(@data_string,Suggestor::Algorithms::PearsonCorrelation)
28
44
  expected = [["2", 0.0], ["1", 0.0]]
29
45
  @suggestor.similar_to("3").must_be :==, expected
30
46
  end
@@ -39,5 +55,10 @@ require_relative '../lib/suggestor'
39
55
  @suggestor.similar_related_to("2").must_be :==, expected
40
56
  end
41
57
 
58
+ it "must return items that might follow a sequence" do
59
+ expected = [["5", 0.29166666666666663], ["3", 0.29166666666666663], ["4", 0.16666666666666666]]
60
+ @suggestor.items_for_set(['1', '2']).must_be :==, expected
61
+ end
62
+
42
63
  end
43
- end
64
+ end
metadata CHANGED
@@ -1,38 +1,39 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: suggestor
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.8
4
5
  prerelease:
5
- version: 0.0.6
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Alvaro Pereyra
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-09-24 00:00:00 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
12
+ date: 2014-04-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
16
15
  name: rake
17
- prerelease: false
18
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
19
17
  none: false
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: "0"
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
24
22
  type: :runtime
25
- version_requirements: *id001
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
26
30
  description: Suggestor allows you to get suggestions of related items in your data
27
- email:
31
+ email:
28
32
  - alvaro@xendacentral.com
29
33
  executables: []
30
-
31
34
  extensions: []
32
-
33
35
  extra_rdoc_files: []
34
-
35
- files:
36
+ files:
36
37
  - .gitignore
37
38
  - Gemfile
38
39
  - README.md
@@ -42,7 +43,7 @@ files:
42
43
  - lib/suggestor/algorithms/euclidean_distance.rb
43
44
  - lib/suggestor/algorithms/pearson_correlation.rb
44
45
  - lib/suggestor/algorithms/recommendation_algorithm.rb
45
- - lib/suggestor/engine.rb
46
+ - lib/suggestor/algorithms/records.rb
46
47
  - lib/suggestor/version.rb
47
48
  - suggestor.gemspec
48
49
  - test/euclidean_test.rb
@@ -50,34 +51,31 @@ files:
50
51
  - test/numbers.json
51
52
  - test/pearson_correlation.rb
52
53
  - test/suggestor_test.rb
53
- homepage: ""
54
+ homepage: ''
54
55
  licenses: []
55
-
56
56
  post_install_message:
57
57
  rdoc_options: []
58
-
59
- require_paths:
58
+ require_paths:
60
59
  - lib
61
- required_ruby_version: !ruby/object:Gem::Requirement
60
+ required_ruby_version: !ruby/object:Gem::Requirement
62
61
  none: false
63
- requirements:
64
- - - ">="
65
- - !ruby/object:Gem::Version
66
- version: "0"
67
- required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
67
  none: false
69
- requirements:
70
- - - ">="
71
- - !ruby/object:Gem::Version
72
- version: "0"
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
73
72
  requirements: []
74
-
75
73
  rubyforge_project: suggestor
76
- rubygems_version: 1.8.10
74
+ rubygems_version: 1.8.23.2
77
75
  signing_key:
78
76
  specification_version: 3
79
77
  summary: Suggestor allows you to get suggestions of related items in your data
80
- test_files:
78
+ test_files:
81
79
  - test/euclidean_test.rb
82
80
  - test/movies.json
83
81
  - test/numbers.json
@@ -1,36 +0,0 @@
1
- require 'json'
2
-
3
- module Suggestor
4
-
5
- class WrongInputFormat < Exception; end
6
-
7
- class Engine
8
-
9
- def initialize(input, algorithm = Algorithms::EuclideanDistance)
10
- @collection = parse_from_json(input)
11
- @algorithm = algorithm.new(@collection)
12
- end
13
-
14
- def similar_to(item, opts={})
15
- @algorithm.similar_to(item, opts)
16
- end
17
-
18
- def recommended_to(item, opts={})
19
- @algorithm.recommended_to(item, opts)
20
- end
21
-
22
- def similar_related_to(item, opts={})
23
- @algorithm.similar_related_to(item, opts)
24
- end
25
-
26
- private
27
-
28
- def parse_from_json(json)
29
- JSON.parse(json)
30
- rescue Exception => ex
31
- raise WrongInputFormat, "Wrong Data format: #{ex.message}"
32
- end
33
-
34
- end
35
-
36
- end