slippy_tiles_scorer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3535ebc0ef81b4b9096043ac739f65877a793c82e9f115f4faa16339184c359c
4
+ data.tar.gz: f0375a478ee6603038e12fc15f55ecf320c161d2764d30fbd362908bfaf763b9
5
+ SHA512:
6
+ metadata.gz: 0df4fbebb75b3f9b3a4019490695d6ae81e9d59498a1479f72f63f92de17ad756b1e1776787025e06cebeff2770aab067faff79aa6f6a2053757daf7972d5a34
7
+ data.tar.gz: 1c5b8bc92a68023c2fb8fbd84f9bba559eeb1b5eec26b757b7310c22a0aacbf316700736f4c814b31e2871a57b279f26d951d576d8861104c5b2460e10771a6e
data/.rubocop.yml ADDED
@@ -0,0 +1,10 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+ Exclude:
4
+ '*.gemspec'
5
+
6
+ Style/StringLiterals:
7
+ EnforcedStyle: double_quotes
8
+
9
+ Style/StringLiteralsInInterpolation:
10
+ EnforcedStyle: double_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-11-25
4
+
5
+ - Initial release
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # SlippyTilesScorer
2
+
3
+ Calculate scores of map tiles (x, y) on a map. The scores are total, (max)
4
+ clusters, and max squares.
5
+
6
+ To experiment with that code, run `bin/console` for an interactive prompt.
7
+
8
+ ## Installation
9
+
10
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
11
+
12
+ Install the gem and add to the application's Gemfile by executing:
13
+
14
+ ```bash
15
+ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
16
+ ```
17
+
18
+ If bundler is not being used to manage dependencies, install the gem by executing:
19
+
20
+ ```bash
21
+ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ Find some examples below.
27
+
28
+ ### Simple
29
+
30
+ A simple example of how to use the `TileScorer` class, when tinkering in a REPL.
31
+
32
+ ```ruby
33
+ require 'tile_scorer'
34
+
35
+ collection_of_tiles = Set.new
36
+ collection_of_tiles.add([0, 0])
37
+ collection_of_tiles.add([0, 1])
38
+ collection_of_tiles.add([1, 0])
39
+ collection_of_tiles.add([1, 1])
40
+ collection_of_tiles.add([0, 2])
41
+ collection_of_tiles.add([1, 2])
42
+ collection_of_tiles.add([2, 0])
43
+ collection_of_tiles.add([2, 1])
44
+ collection_of_tiles.add([2, 2])
45
+
46
+ tile_scorer = SlippyTilesScorer::Scorer.new(tiles_x_y: collection_of_tiles)
47
+ tile_scorer.valid? # => true
48
+
49
+ # the collection_of_tiles is a 3x3 map and will be used to calculate scores,
50
+ # unless you provide a different set of tiles, when calling the methods.
51
+
52
+ tile_scorer.visited # => 9
53
+
54
+ # calculate clusters
55
+ tile_scorer.clusters # =>
56
+ # {
57
+ # :clusters=>[
58
+ # <Set: {[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [1, 2], [2, 2], [2, 1], [2, 0]}>
59
+ # ],
60
+ # :cluster_tiles=>#<Set: {[1, 1]}>,
61
+ # :clusters_of_cluster_tiles=>[]
62
+ # }
63
+
64
+ # calculate max squares
65
+ tile_scorer.max_squares # => {:size=>3, top_left_tile_x_y=>#<Set: {[0, 0]}>}
66
+ tile_scorer.max_squares(min_size: 4) # => {:size=>4, top_left_tile_x_y=>#<Set: {}>}
67
+ ```
68
+
69
+ ### Optimized
70
+
71
+ An more computationally optimized example of how to use the `TileScorer` class, when using the code in production.
72
+
73
+ ```ruby
74
+ ### OPTIMIZE COMPUTATION ###
75
+ tile_scorer = SlippyTilesScorer::Scorer.new(tiles_x_y: collection_of_tiles)
76
+ # Optimize computation by calculating clusters first,
77
+ result_clusters = tile_scorer.clusters
78
+ clusters = result_clusters[:clusters]
79
+ # then max squares based on the clusters.
80
+ max_squares_of_clusters = clusters.flat_map do |cluster|
81
+ tile_scorer.max_squares(tiles_x_y: cluster)
82
+ end
83
+ # Find the max squares with the biggest edge size.
84
+ max_squares_max_size_mapped = max_squares_of_clusters.map do |max_square|
85
+ max_square[:size]
86
+ end
87
+ max_squares_max_size = max_squares_max_size_mapped.max
88
+ # Select and keep only the max squares with the biggest edge size.
89
+ max_squares = max_squares_of_clusters.select do |max_square|
90
+ max_square[:size] == max_squares_max_size
91
+ end
92
+ puts max_squares # =>[{:size=>3, top_left_tile_x_y=>#<Set: {[0, 0]}>}]
93
+ ```
94
+
95
+ ## Development
96
+
97
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
98
+
99
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
100
+
101
+ ## Contributing
102
+
103
+ Bug reports and pull requests are welcome on GitHub at https://github.com/simonneutert/slippy_tiles_scorer.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+ RuboCop::RakeTask.new
6
+
7
+ task default: %i[test rubocop]
8
+
9
+ # TODO: add more tasks here
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlippyTilesScorer
4
+ # finds clusters in a collection/tiles_x_y of points (x, y)
5
+ class Cluster
6
+ attr_accessor :tiles_x_y
7
+
8
+ def initialize(tiles_x_y: Set.new)
9
+ @tiles_x_y = Marshal.load(Marshal.dump(tiles_x_y))
10
+ @cluster_tiles = Set.new
11
+ @visited = Set.new
12
+ @clusters = []
13
+ end
14
+
15
+ def clusters
16
+ while @tiles_x_y.any?
17
+ start = @tiles_x_y.first
18
+ @tiles_x_y.delete(start)
19
+ @visited.add(start)
20
+ find_cluster_around(start)
21
+ end
22
+ { clusters: @clusters, cluster_tiles: @cluster_tiles }
23
+ end
24
+
25
+ private
26
+
27
+ def find_cluster_around(start)
28
+ cluster = Set.new
29
+ cluster.add(start)
30
+ todo = [start]
31
+
32
+ cluster = broad_search(todo, cluster)
33
+
34
+ @clusters.push(cluster) if cluster.size > 1
35
+ cluster
36
+ end
37
+
38
+ def neighbors_up_down_left_right?(neighbors)
39
+ neighbors.all? { |n| @tiles_x_y.include?(n) || @visited.include?(n) }
40
+ end
41
+
42
+ def broad_search(todo, cluster) # rubocop:disable Metrics/MethodLength
43
+ until todo.empty?
44
+ point = todo.pop
45
+ neighbors = neighbor_points(*point)
46
+
47
+ next if neighbors.empty?
48
+
49
+ @cluster_tiles.add(point) if neighbors_up_down_left_right?(neighbors)
50
+ neighbors.each do |neighbor|
51
+ next unless @tiles_x_y.include?(neighbor)
52
+ next if @visited.include?(neighbor)
53
+
54
+ @visited.add(neighbor)
55
+ cluster.add(neighbor)
56
+ todo.push(neighbor)
57
+ end
58
+ end
59
+ cluster
60
+ end
61
+
62
+ def neighbor_points(x, y) # rubocop:disable Naming/MethodParameterName
63
+ res = Set.new
64
+ [[x - 1, y], [x + 1, y], [x, y + 1], [x, y - 1]].each { |point| res.add(point) }
65
+ res
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlippyTilesScorer
4
+ # finds the maximum square in a collection/tiles_x_y of points (x, y)
5
+ class MaxSquare
6
+ attr_accessor :tiles_x_y
7
+
8
+ def initialize(tiles_x_y: Set.new)
9
+ @tiles_x_y = Marshal.load(Marshal.dump(tiles_x_y))
10
+ end
11
+
12
+ # @param min_size [Integer] The minimum size of the square, should be 3 or greater.
13
+ # As the smallest square is 3x3 and therefor a cluster tile needs to be the center.
14
+ # @return [Hash] The size of the square and the points of the square.
15
+ def max_squares(min_size: 3)
16
+ raise ArgumentError, 'min_size must be 2 or greater' if min_size < 2
17
+
18
+ result = { size: min_size, top_left_tile_x_y: Set.new }
19
+ @tiles_x_y.each do |(x, y)|
20
+ raise ArgumentError, 'x and y must be greater than or equal to 0' if x.negative? || y.negative?
21
+
22
+ steps = max_square(x: x, y: y)
23
+ track_result(result: result, steps: steps, x: x, y: y) if steps >= min_size
24
+ end
25
+ result
26
+ end
27
+
28
+ def max_square(x:, y:) # rubocop:disable Naming/MethodParameterName
29
+ steps = 1
30
+ steps += 1 while steps_fulfilled?(x: x, y: y, steps: steps)
31
+ steps
32
+ end
33
+
34
+ def steps_fulfilled?(x:, y:, steps:) # rubocop:disable Naming/MethodParameterName
35
+ @tiles_x_y.include?([x + steps, y + steps]) &&
36
+ (0...steps).all? do |i|
37
+ @tiles_x_y.include?([x + steps, y + i]) &&
38
+ @tiles_x_y.include?([x + i, y + steps])
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def track_result(result:, steps:, x:, y:) # rubocop:disable Naming/MethodParameterName
45
+ if steps > result[:size]
46
+ result[:size] = steps
47
+ result[:top_left_tile_x_y] = Set.new
48
+ result[:top_left_tile_x_y] << [x, y]
49
+ elsif steps == result[:size]
50
+ result[:top_left_tile_x_y] << [x, y]
51
+ end
52
+ result
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlippyTilesScorer
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "slippy_tiles_scorer/version"
4
+ require_relative "./slippy_tiles_scorer/cluster"
5
+ require_relative "./slippy_tiles_scorer/max_square"
6
+
7
+ module SlippyTilesScorer
8
+ class Error < StandardError; end
9
+
10
+ # Score class to calculate the score of a collection of points (x, y)
11
+ class Score
12
+ attr_accessor :tiles_x_y
13
+
14
+ def initialize(tiles_x_y: Set.new)
15
+ @tiles_x_y = tiles_x_y
16
+ end
17
+
18
+ def valid?
19
+ return true if @tiles_x_y.empty?
20
+
21
+ raise ArgumentError, "@tiles_x_y must be a Set" unless @tiles_x_y.is_a?(Set)
22
+
23
+ set_of_arrays?
24
+ set_of_arrays_of_integers?
25
+ end
26
+
27
+ def clusters(tiles_x_y: @tiles_x_y)
28
+ service = SlippyTilesScorer::Cluster.new(tiles_x_y: tiles_x_y)
29
+ result = service.clusters
30
+ result[:clusters_of_cluster_tiles] =
31
+ SlippyTilesScorer::Cluster.new(tiles_x_y: result[:cluster_tiles]).clusters[:clusters]
32
+ result
33
+ end
34
+
35
+ def max_square(x:, y:, tiles_x_y: @tiles_x_y) # rubocop:disable Naming/MethodParameterName
36
+ SlippyTilesScorer::MaxSquare.new(tiles_x_y: tiles_x_y).max_square(x: x, y: y)
37
+ end
38
+
39
+ def max_squares(tiles_x_y: @tiles_x_y, min_size: 3)
40
+ SlippyTilesScorer::MaxSquare.new(tiles_x_y: tiles_x_y).max_squares(min_size: min_size)
41
+ end
42
+
43
+ def steps_fulfilled?(x:, y:, steps:) # rubocop:disable Naming/MethodParameterName
44
+ SlippyTilesScorer::MaxSquare.new(tiles_x_y: @tiles_x_y).steps_fulfilled?(x: x, y: y, steps: steps)
45
+ end
46
+
47
+ def visited(tiles_x_y: @tiles_x_y)
48
+ tiles_x_y.size
49
+ end
50
+
51
+ private
52
+
53
+ def set_of_arrays?
54
+ return if @tiles_x_y.all? { |point| point.is_a?(Array) && point.size == 2 }
55
+
56
+ raise ArgumentError, "each point must be an array with two elements"
57
+ end
58
+
59
+ def set_of_arrays_of_integers?
60
+ return if @tiles_x_y.all? { |point| point.all? { |coord| coord.is_a?(Integer) } }
61
+
62
+ raise ArgumentError, "each point must be an array with two integers"
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,4 @@
1
+ module SlippyTilesScorer
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slippy_tiles_scorer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Simon Neutert
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-11-25 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: For a given set of slippy tiles, this gem calculates the score of the
14
+ tiles, like total tiles, clusters and max squares.
15
+ email:
16
+ - simonneutert@users.noreply.github.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".rubocop.yml"
22
+ - CHANGELOG.md
23
+ - README.md
24
+ - Rakefile
25
+ - lib/slippy_tiles_scorer.rb
26
+ - lib/slippy_tiles_scorer/cluster.rb
27
+ - lib/slippy_tiles_scorer/max_square.rb
28
+ - lib/slippy_tiles_scorer/version.rb
29
+ - sig/slippy_tiles_scorer.rbs
30
+ homepage: https://github.com/simonneutert/slippy_tiles_scorer
31
+ licenses: []
32
+ metadata:
33
+ homepage_uri: https://github.com/simonneutert/slippy_tiles_scorer
34
+ source_code_uri: https://github.com/simonneutert/slippy_tiles_scorer
35
+ changelog_uri: https://github.com/simonneutert/slippy_tiles_scorer/blob/main/CHANGELOG.md
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 3.0.0
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.5.22
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: A gem to score a set of slippy tiles.
55
+ test_files: []