slippy_tiles_scorer 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3535ebc0ef81b4b9096043ac739f65877a793c82e9f115f4faa16339184c359c
4
- data.tar.gz: f0375a478ee6603038e12fc15f55ecf320c161d2764d30fbd362908bfaf763b9
3
+ metadata.gz: ce623fd11c56fc5615043d62fd196810c7c0caccd5f9d9789dfaa864954ed2ee
4
+ data.tar.gz: 0b7296978405915b2ee53d79da2a8e8c0cc15445f2b71ce5e396ca5fc7af5a39
5
5
  SHA512:
6
- metadata.gz: 0df4fbebb75b3f9b3a4019490695d6ae81e9d59498a1479f72f63f92de17ad756b1e1776787025e06cebeff2770aab067faff79aa6f6a2053757daf7972d5a34
7
- data.tar.gz: 1c5b8bc92a68023c2fb8fbd84f9bba559eeb1b5eec26b757b7310c22a0aacbf316700736f4c814b31e2871a57b279f26d951d576d8861104c5b2460e10771a6e
6
+ metadata.gz: 746ffc5f6d06857aec00c471b3c671612debe31c22a2cdb1d73b1da3583ba253008cbea5e886addf44961019cd2435db6eccb7f70086f182e3cfa3440247a88a
7
+ data.tar.gz: 7e6bacaaab5a06b40d0fee46a3494f436a96b04e042a90bae45ae567ab1860b99499999e66089a372be84d733ae4f15683316748ee1835604c3358acdb3b2da8
data/.rubocop.yml CHANGED
@@ -1,10 +1,19 @@
1
+ require:
2
+ - rubocop-rake
3
+ - rubocop-performance
4
+
1
5
  AllCops:
2
6
  TargetRubyVersion: 3.0
7
+ NewCops: enable
3
8
  Exclude:
4
- '*.gemspec'
9
+ - "*.gemspec"
10
+ - "./vendor/bundle/**/*"
5
11
 
6
12
  Style/StringLiterals:
7
13
  EnforcedStyle: double_quotes
8
14
 
9
15
  Style/StringLiteralsInInterpolation:
10
16
  EnforcedStyle: double_quotes
17
+
18
+ Naming/MethodParameterName:
19
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2024-11-25
3
+ ## [0.0.3] - 2024-11-26
4
+
5
+ - More tests, like a json file with test data.
6
+ - The `Rakefile` can be used to run the tests and check the code style.
7
+ - A CI pipeline runs the test suite and checks the code style for multiple Ruby versions >= v3.
8
+ - Rubocop was configured and some plugins were added to the `.rubocop.yml` file.
9
+
10
+ ## [0.0.2] - 2024-11-26
11
+
12
+ - The result `Hash` of `#clusters` under the key `:clusters` is now an array of `[x, y]` and `x` and `y` being integer values.
13
+
14
+ ## [0.0.1] - 2024-11-25
4
15
 
5
16
  - Initial release
data/README.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # SlippyTilesScorer
2
2
 
3
+ > [!NOTE]
4
+ > This gem's API is not considered stable yet.
5
+ > Things might change in the future.
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/slippy_tiles_scorer.svg)](https://badge.fury.io/rb/slippy_tiles_scorer) \
8
+ [![Ruby](https://github.com/simonneutert/slippy_tiles_scorer/actions/workflows/main.yml/badge.svg)](https://github.com/simonneutert/slippy_tiles_scorer/actions/workflows/main.yml)
9
+
3
10
  Calculate scores of map tiles (x, y) on a map. The scores are total, (max)
4
11
  clusters, and max squares.
5
12
 
@@ -7,18 +14,16 @@ To experiment with that code, run `bin/console` for an interactive prompt.
7
14
 
8
15
  ## Installation
9
16
 
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
17
  Install the gem and add to the application's Gemfile by executing:
13
18
 
14
19
  ```bash
15
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
20
+ bundle add slippy_tiles_scorer
16
21
  ```
17
22
 
18
23
  If bundler is not being used to manage dependencies, install the gem by executing:
19
24
 
20
25
  ```bash
21
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
26
+ gem install slippy_tiles_scorer
22
27
  ```
23
28
 
24
29
  ## Usage
@@ -43,7 +48,7 @@ collection_of_tiles.add([2, 0])
43
48
  collection_of_tiles.add([2, 1])
44
49
  collection_of_tiles.add([2, 2])
45
50
 
46
- tile_scorer = SlippyTilesScorer::Scorer.new(tiles_x_y: collection_of_tiles)
51
+ tile_scorer = SlippyTilesScorer::Score.new(tiles_x_y: collection_of_tiles)
47
52
  tile_scorer.valid? # => true
48
53
 
49
54
  # the collection_of_tiles is a 3x3 map and will be used to calculate scores,
@@ -55,15 +60,17 @@ tile_scorer.visited # => 9
55
60
  tile_scorer.clusters # =>
56
61
  # {
57
62
  # :clusters=>[
58
- # <Set: {[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [1, 2], [2, 2], [2, 1], [2, 0]}>
63
+ # [
64
+ # [0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [1, 2], [2, 2], [2, 1], [2, 0]
65
+ # ]
59
66
  # ],
60
67
  # :cluster_tiles=>#<Set: {[1, 1]}>,
61
- # :clusters_of_cluster_tiles=>[]
68
+ # :clusters_of_cluster_tiles=>[[[1, 1]]]
62
69
  # }
63
70
 
64
71
  # 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: {}>}
72
+ tile_scorer.max_squares # => {:size=>0, top_left_tile_x_y=>#<Set: {[0, 0]}>}
73
+ tile_scorer.max_squares(min_size: 4) # => {:size=>0, top_left_tile_x_y=>#<Set: {}>}
67
74
  ```
68
75
 
69
76
  ### Optimized
@@ -72,7 +79,7 @@ An more computationally optimized example of how to use the `TileScorer` class,
72
79
 
73
80
  ```ruby
74
81
  ### OPTIMIZE COMPUTATION ###
75
- tile_scorer = SlippyTilesScorer::Scorer.new(tiles_x_y: collection_of_tiles)
82
+ tile_scorer = SlippyTilesScorer::Score.new(tiles_x_y: collection_of_tiles)
76
83
  # Optimize computation by calculating clusters first,
77
84
  result_clusters = tile_scorer.clusters
78
85
  clusters = result_clusters[:clusters]
data/Rakefile CHANGED
@@ -6,4 +6,7 @@ RuboCop::RakeTask.new
6
6
 
7
7
  task default: %i[test rubocop]
8
8
 
9
- # TODO: add more tasks here
9
+ desc "Run tests"
10
+ task :test do
11
+ sh "ruby test_runner.rb"
12
+ end
@@ -1,68 +1,86 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
3
5
  module SlippyTilesScorer
4
6
  # finds clusters in a collection/tiles_x_y of points (x, y)
5
7
  class Cluster
6
8
  attr_accessor :tiles_x_y
7
9
 
8
10
  def initialize(tiles_x_y: Set.new)
9
- @tiles_x_y = Marshal.load(Marshal.dump(tiles_x_y))
11
+ @tiles_x_y = tiles_x_y
10
12
  @cluster_tiles = Set.new
11
- @visited = Set.new
13
+ @visited = {}
12
14
  @clusters = []
13
15
  end
14
16
 
17
+ # @return [Hash] The clusters and the tiles in the clusters.
15
18
  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)
19
+ @tiles_x_y.each do |i|
20
+ next if visited?(i[0], i[1])
21
+
22
+ visit!(i[0], i[1])
23
+ find_cluster_around(i)
21
24
  end
22
25
  { clusters: @clusters, cluster_tiles: @cluster_tiles }
23
26
  end
24
27
 
25
28
  private
26
29
 
30
+ # @param start [Array] The x and y coordinate of the start point.
31
+ # @return [Array<Array<Integer, Integer>>] The cluster of points.
27
32
  def find_cluster_around(start)
28
- cluster = Set.new
29
- cluster.add(start)
33
+ cluster = []
34
+ cluster.push(start)
30
35
  todo = [start]
36
+ broad_search!(todo, cluster)
31
37
 
32
- cluster = broad_search(todo, cluster)
33
-
34
- @clusters.push(cluster) if cluster.size > 1
38
+ @clusters.push(cluster) if cluster.size.positive?
35
39
  cluster
36
40
  end
37
41
 
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
42
+ # @param todo [Array] The points to visit.
43
+ # @param cluster [Array] The cluster of points.
44
+ def broad_search!(todo, cluster) # rubocop:disable Metrics/CyclomaticComplexity
43
45
  until todo.empty?
44
46
  point = todo.pop
45
47
  neighbors = neighbor_points(*point)
46
-
47
- next if neighbors.empty?
48
-
49
- @cluster_tiles.add(point) if neighbors_up_down_left_right?(neighbors)
48
+ neighbors_up_down_left_right?(neighbors) && @cluster_tiles.add(point)
50
49
  neighbors.each do |neighbor|
50
+ next if visited?(neighbor[0], neighbor[1])
51
51
  next unless @tiles_x_y.include?(neighbor)
52
- next if @visited.include?(neighbor)
53
52
 
54
- @visited.add(neighbor)
55
- cluster.add(neighbor)
56
- todo.push(neighbor)
53
+ visit!(*neighbor) && todo.push(neighbor) && cluster.push(neighbor)
57
54
  end
58
55
  end
59
- cluster
60
56
  end
61
57
 
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
58
+ # @param x [Integer] The x coordinate of the point.
59
+ # @param y [Integer] The y coordinate of the point.
60
+ # @return [Boolean] True if the point is visited.
61
+ def visit!(x, y)
62
+ @visited[x] ||= {}
63
+ @visited[x][y] = true
64
+ end
65
+
66
+ # @param x [Integer] The x coordinate of the point.
67
+ # @param y [Integer] The y coordinate of the point.
68
+ # @return [Boolean|Nil] True if the point is visited.
69
+ def visited?(x, y)
70
+ @visited.dig(x, y)
71
+ end
72
+
73
+ # @param neighbors [Array] The neighbors of the point.
74
+ # @return [Boolean] True if all neighbors are in the tiles_x_y.
75
+ def neighbors_up_down_left_right?(neighbors)
76
+ neighbors.all? { |n| @tiles_x_y.include?(n) }
77
+ end
78
+
79
+ # @param x [Integer] The x coordinate of the point.
80
+ # @param y [Integer] The y coordinate of the point.
81
+ # @return [Array<Array<Integer, Integer>>] The neighbors of the point.
82
+ def neighbor_points(x, y)
83
+ [[x - 1, y], [x + 1, y], [x, y + 1], [x, y - 1]]
66
84
  end
67
85
  end
68
86
  end
@@ -1,54 +1,106 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
3
5
  module SlippyTilesScorer
4
6
  # finds the maximum square in a collection/tiles_x_y of points (x, y)
5
7
  class MaxSquare
6
8
  attr_accessor :tiles_x_y
7
9
 
8
10
  def initialize(tiles_x_y: Set.new)
9
- @tiles_x_y = Marshal.load(Marshal.dump(tiles_x_y))
11
+ @tiles_x_y = tiles_x_y
12
+ @tiles_lut = nil
10
13
  end
11
14
 
12
15
  # @param min_size [Integer] The minimum size of the square, should be 3 or greater.
13
16
  # As the smallest square is 3x3 and therefor a cluster tile needs to be the center.
14
17
  # @return [Hash] The size of the square and the points of the square.
15
18
  def max_squares(min_size: 3)
16
- raise ArgumentError, 'min_size must be 2 or greater' if min_size < 2
19
+ @max_size_found = 0
20
+ raise ArgumentError, "min_size must be 2 or greater" if min_size < 2
21
+
22
+ result = max_square_result(min_size: min_size)
23
+ @max_size_found = 0
24
+ @tiles_lut = nil
25
+ result
26
+ end
17
27
 
18
- result = { size: min_size, top_left_tile_x_y: Set.new }
28
+ # @param min_size [Integer] The minimum size of the square, should be 3 or greater.
29
+ # @return [Hash] The size of the square and the points of the square.
30
+ def max_square_result(min_size: 3)
31
+ result = { size: @max_size_found, top_left_tile_x_y: Set.new }
19
32
  @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?
33
+ raise ArgumentError, "x and y must be greater than or equal to 0" if x.negative? || y.negative?
21
34
 
22
35
  steps = max_square(x: x, y: y)
23
- track_result(result: result, steps: steps, x: x, y: y) if steps >= min_size
36
+ result = track_result(result: result, steps: steps, x: x, y: y) if steps >= min_size
24
37
  end
25
38
  result
26
39
  end
27
40
 
28
- def max_square(x:, y:) # rubocop:disable Naming/MethodParameterName
41
+ # @param x [Integer] The x coordinate of the top left tile.
42
+ # @param y [Integer] The y coordinate of the top left tile.
43
+ # @return [Integer] The size of the square.
44
+ def max_square(x:, y:)
29
45
  steps = 1
30
46
  steps += 1 while steps_fulfilled?(x: x, y: y, steps: steps)
31
47
  steps
32
48
  end
33
49
 
34
- def steps_fulfilled?(x:, y:, steps:) # rubocop:disable Naming/MethodParameterName
35
- @tiles_x_y.include?([x + steps, y + steps]) &&
50
+ # @param x [Integer] The x coordinate of the top left tile.
51
+ # @param y [Integer] The y coordinate of the top left tile.
52
+ # @param steps [Integer] The size of the square.
53
+ # @return [Boolean] True if all steps are fulfilled.
54
+ def steps_fulfilled?(x:, y:, steps:)
55
+ in_lut?(x: x + steps, y: y + steps) &&
36
56
  (0...steps).all? do |i|
37
- @tiles_x_y.include?([x + steps, y + i]) &&
38
- @tiles_x_y.include?([x + i, y + steps])
57
+ in_lut?(x: x + steps, y: y + i) &&
58
+ in_lut?(x: x + i, y: y + steps)
39
59
  end
40
60
  end
41
61
 
42
62
  private
43
63
 
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]
64
+ # This is as fast as it gets! Do not implement `#nil?
65
+ #
66
+ # @param x [Integer] The x coordinate of the top left tile.
67
+ # @param y [Integer] The y coordinate of the top left tile.
68
+ # @return [Boolean|Nil] True if the tile is in the tiles_lut.
69
+ def in_lut?(x:, y:)
70
+ unless @tiles_lut
71
+ @tiles_lut = {}
72
+ @tiles_x_y.each do |(x, y)|
73
+ @tiles_lut[x] ||= {}
74
+ @tiles_lut[x][y] = true
75
+ end
51
76
  end
77
+ @tiles_lut.dig(x, y)
78
+ end
79
+
80
+ # @param result [Hash] The result hash.
81
+ # @param steps [Integer] The size of the square.
82
+ # @param x [Integer] The x coordinate of the top left tile.
83
+ # @param y [Integer] The y coordinate of the top left tile.
84
+ # @return [Hash] The updated result hash.
85
+ def track_result(result:, steps:, x:, y:)
86
+ if steps > @max_size_found
87
+ @max_size_found = steps
88
+ result = { size: @max_size_found, top_left_tile_x_y: Set.new }
89
+ update_result!(result: result, steps: steps, x: x, y: y)
90
+ elsif steps == @max_size_found
91
+ update_result!(result: result, steps: steps, x: x, y: y)
92
+ end
93
+ result
94
+ end
95
+
96
+ # @param result [Hash] The result hash.
97
+ # @param steps [Integer] The size of the square.
98
+ # @param x [Integer] The x coordinate of the top left tile.
99
+ # @param y [Integer] The y coordinate of the top left tile.
100
+ # @return [Hash] The updated result hash.
101
+ def update_result!(result:, steps:, x:, y:)
102
+ result[:size] = steps
103
+ result[:top_left_tile_x_y] << [x, y]
52
104
  result
53
105
  end
54
106
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SlippyTilesScorer
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.3"
5
5
  end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
3
4
  require_relative "slippy_tiles_scorer/version"
4
- require_relative "./slippy_tiles_scorer/cluster"
5
- require_relative "./slippy_tiles_scorer/max_square"
5
+ require_relative "slippy_tiles_scorer/cluster"
6
+ require_relative "slippy_tiles_scorer/max_square"
6
7
 
7
8
  module SlippyTilesScorer
8
9
  class Error < StandardError; end
@@ -17,11 +18,9 @@ module SlippyTilesScorer
17
18
 
18
19
  def valid?
19
20
  return true if @tiles_x_y.empty?
20
-
21
21
  raise ArgumentError, "@tiles_x_y must be a Set" unless @tiles_x_y.is_a?(Set)
22
22
 
23
- set_of_arrays?
24
- set_of_arrays_of_integers?
23
+ set_of_arrays? && set_of_arrays_of_integers?
25
24
  end
26
25
 
27
26
  def clusters(tiles_x_y: @tiles_x_y)
@@ -32,7 +31,7 @@ module SlippyTilesScorer
32
31
  result
33
32
  end
34
33
 
35
- def max_square(x:, y:, tiles_x_y: @tiles_x_y) # rubocop:disable Naming/MethodParameterName
34
+ def max_square(x:, y:, tiles_x_y: @tiles_x_y)
36
35
  SlippyTilesScorer::MaxSquare.new(tiles_x_y: tiles_x_y).max_square(x: x, y: y)
37
36
  end
38
37
 
@@ -40,7 +39,7 @@ module SlippyTilesScorer
40
39
  SlippyTilesScorer::MaxSquare.new(tiles_x_y: tiles_x_y).max_squares(min_size: min_size)
41
40
  end
42
41
 
43
- def steps_fulfilled?(x:, y:, steps:) # rubocop:disable Naming/MethodParameterName
42
+ def steps_fulfilled?(x:, y:, steps:)
44
43
  SlippyTilesScorer::MaxSquare.new(tiles_x_y: @tiles_x_y).steps_fulfilled?(x: x, y: y, steps: steps)
45
44
  end
46
45
 
@@ -51,13 +50,13 @@ module SlippyTilesScorer
51
50
  private
52
51
 
53
52
  def set_of_arrays?
54
- return if @tiles_x_y.all? { |point| point.is_a?(Array) && point.size == 2 }
53
+ return true if @tiles_x_y.all? { |point| point.is_a?(Array) && point.size == 2 }
55
54
 
56
55
  raise ArgumentError, "each point must be an array with two elements"
57
56
  end
58
57
 
59
58
  def set_of_arrays_of_integers?
60
- return if @tiles_x_y.all? { |point| point.all? { |coord| coord.is_a?(Integer) } }
59
+ return true if @tiles_x_y.all? { |point| point.all?(Integer) }
61
60
 
62
61
  raise ArgumentError, "each point must be an array with two integers"
63
62
  end
data/test_helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlippyTilesScorer
4
+ # TestHelper class to help with testing
5
+ class TestHelper
6
+ class << self
7
+ def stub_tiles_x_y(service:, size: 50)
8
+ service.tiles_x_y = Set.new
9
+ (0...size).each do |i|
10
+ (0...size).each do |j|
11
+ service.tiles_x_y.add([i, j])
12
+ end
13
+ end
14
+ service
15
+ end
16
+ end
17
+ end
18
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slippy_tiles_scorer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Neutert
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-25 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-11-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: set
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
13
27
  description: For a given set of slippy tiles, this gem calculates the score of the
14
28
  tiles, like total tiles, clusters and max squares.
15
29
  email:
@@ -27,6 +41,7 @@ files:
27
41
  - lib/slippy_tiles_scorer/max_square.rb
28
42
  - lib/slippy_tiles_scorer/version.rb
29
43
  - sig/slippy_tiles_scorer.rbs
44
+ - test_helper.rb
30
45
  homepage: https://github.com/simonneutert/slippy_tiles_scorer
31
46
  licenses: []
32
47
  metadata: