run_length_encoding_rb 0.2.1 → 1.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79d5976a4c25210fa4e2ac722b4d25f8e8d69d28527cad8ac38965f35aa97026
4
- data.tar.gz: 8b5547077da37fb7ba5870b37783b1d30ae2eb0d6fa949bb0bd1d82e38b0dd0b
3
+ metadata.gz: 58c5fa25ae5085cbc17f1107dcf7df5c7ac13014613df72bb9c235fbadb56a22
4
+ data.tar.gz: c4406fd2bccb4b6942764cc8baa581c9bcbfad7b6d54562afbaac6a88b652f6a
5
5
  SHA512:
6
- metadata.gz: 9343ec73fa5899b7af382825918bac63e2357a15f05707750a22061d85f7b3a62a54070e5bc4114c0cdc1d7d0da7c764ce525de5162405cc6f46ca02ef3eac29
7
- data.tar.gz: e5f1be41e8af7f2528b542a623a134983a447a4b0ff9dadf411951c83f917458f633f2b92f6958f1e0f34a6803cc47b21be1d78539d214189b39aa4af069ace0
6
+ metadata.gz: 3c5fd9a1be1c71c03d7c2b50a68469f76240acb5997d26d0700f5eb23e8f159f413609ced27abe1b7522c3dd2368bb699ee75980891efb150ca1d64cc8132da4
7
+ data.tar.gz: d7c3e0783c080bd4d1d8df55c5bc305d17fd233281d1694feb30b88aebe29538f95068a575e828d50a48a9780853b60946355575383f2b990fe2aa9d77c3d534
data/.rubocop.yml CHANGED
@@ -1,3 +1,6 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+
1
4
  Style/StringLiterals:
2
5
  Enabled: true
3
6
  EnforcedStyle: double_quotes
@@ -10,4 +13,4 @@ Layout/LineLength:
10
13
  Max: 120
11
14
 
12
15
  Metrics/MethodLength:
13
- Max: 25
16
+ Max: 10
data/CHANGELOG.md CHANGED
@@ -1,16 +1,3 @@
1
- ### 0.2.1 / 2023-01-25
1
+ ### 1.0.0 / 2023-10-21
2
2
 
3
- * Updated docs.
4
-
5
- ### 0.2.0 / 2023-01-25
6
-
7
- * Improvements:
8
- * Decoding is implemented now;
9
- * Code was refactored;
10
- * Changed gem's name to pass RubyGems.org name filter;
11
- * Now gem is available at RubyGems.org.
12
-
13
- ### 0.1.0 / 2022-12-15
14
-
15
- * Initial release:
16
- * Only encoding is implemented yet.
3
+ * New API, code refactoring, bug fixes, tests.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- run_length_encoding_rb (0.2.0)
4
+ run_length_encoding_rb (0.2.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -37,4 +37,4 @@ DEPENDENCIES
37
37
  run_length_encoding_rb!
38
38
 
39
39
  BUNDLED WITH
40
- 2.2.3
40
+ 2.4.19
data/README.md CHANGED
@@ -1,13 +1,15 @@
1
+ # run_length_encoding_rb [![Gem Version](https://badge.fury.io/rb/run_length_encoding_rb.svg)](https://badge.fury.io/rb/run_length_encoding_rb)
2
+
1
3
  ## Description
2
4
 
3
- A simple gem that does run-length encoding.
5
+ Run-length encoding for Ruby.
4
6
 
5
7
  ## Installation
6
8
 
7
- Add this line to your application's Gemfile:
9
+ Add this line to your application"s Gemfile:
8
10
 
9
11
  ```ruby
10
- gem 'run_length_encoding_rb'
12
+ gem "run_length_encoding_rb"
11
13
  ```
12
14
 
13
15
  And then execute:
@@ -22,7 +24,7 @@ Or install it yourself as:
22
24
 
23
25
  ### Encoding
24
26
 
25
- obj.encode(data [, separator]) -> array
27
+ RunLengthEncodingRb.encode(data [, separator]) -> array
26
28
 
27
29
  #### Arguments
28
30
 
@@ -35,44 +37,37 @@ obj.encode(data [, separator]) -> array
35
37
 
36
38
  + _separator_
37
39
 
38
- A String or Regexp to split the string into single elements. Used only if _data_ is a String.
40
+ A String or Regexp to split the _data_ string into single elements. Used only if _data_ is a String.
39
41
 
40
42
  #### Returns
41
43
 
42
- + _Array<Hash{:chunk => Object, :count => Integer}>_
44
+ + _Array\<RunLengthEncodingRb::RLEElement\>_
43
45
 
44
- Encoded data in the following format:
45
- Array<Hash{:chunk => Object, :count => Integer}>, where:
46
- - :chunk => Object: a repeated element;
47
- - :count => Integer: how many times the element is repeated.
46
+ Encoded data. Each element is a RunLengthEncodingRb::RLEElement object with the attributes #chunk (the repeated element) and #run_length (how many times the element is repeated).
48
47
 
49
48
  ### Encoding examples
50
49
 
51
50
  ```ruby
52
- require 'run_length_encoding'
51
+ require "run_length_encoding_rb"
53
52
 
54
- rle = RunLengthEncoding.new
53
+ RLE = RunLengthEncodingRb
55
54
 
56
55
  # Encode an array:
57
56
  a = %w[foo foo bar foo foo foo]
57
+ RLE.encode(a)
58
58
 
59
- rle.encode(a)
60
- # => [{:chunk=>"foo", :count=>2}, {:chunk=>"bar", :count=>1}, {:chunk=>"foo", :count=>3}]
61
-
62
- # Encode a string with a default separator (each character is treated as a single element):
63
- str = 'foo'
64
- rle.encode(str)
65
- # => [{:chunk=>"f", :count=>1}, {:chunk=>"o", :count=>2}]
59
+ # Encode a string with a default separator (each character
60
+ # will be treated as a single element):
61
+ str = "foo"
62
+ RLE.encode(str)
66
63
 
67
- # Encode a string with a custom separator:
68
- str = 'foo_foo_bar'
69
- rle.encode(str, '_')
70
- # => [{:chunk=>"foo", :count=>2}, {:chunk=>"bar", :count=>1}]
64
+ # Encode a string with a explicit separator:
65
+ str = "foo_foo_bar"
66
+ RLE.encode(str, "_")
71
67
 
72
68
  # Encode an enumerator:
73
- str = 'foo'
74
- rle.encode(str.each_byte)
75
- # => [{:chunk=>102, :count=>1}, {:chunk=>111, :count=>2}]
69
+ str = "foo"
70
+ RLE.encode(str.each_byte)
76
71
  ```
77
72
 
78
73
  ### Decoding
@@ -83,35 +78,29 @@ obj.decode(data) -> array
83
78
 
84
79
  + _data_
85
80
 
86
- Data to decode in the following format:
87
- Array<Hash{:chunk => Object, :count => Integer}>, where:
88
- - :chunk => Object: a repeated element;
89
- + :count => Integer: how many times the element is repeated.
81
+ Array of RunLengthEncodingRb::RLEElement (or any duck-typed objects, which have the obj#chunk and obj#run_length attributes).
90
82
 
91
83
  #### Returns
92
84
 
93
- Array<Object>
85
+ + Array\<Object\>
94
86
 
95
- Decoded data.
96
- <!-- -->
87
+ Decoded data.
97
88
 
98
89
  ### Decoding example
99
90
 
100
91
  ```ruby
101
- require 'run_length_encoding'
92
+ require "run_length_encoding_rb"
102
93
 
103
- rle = RunLengthEncoding.new
94
+ RLE = RunLengthEncodingRb
104
95
 
105
- # Decode data of mixed types:
106
96
  data = [
107
- { count: 3, chunk: "foo" },
108
- { count: 1, chunk: "bar" },
109
- { count: 2, chunk: nil },
110
- { count: 4, chunk: 1 }
97
+ RunLengthEncodingRb::RLEElement.new(chunk: "foo", run_length: 3),
98
+ RunLengthEncodingRb::RLEElement.new(chunk: "bar", run_length: 1),
99
+ RunLengthEncodingRb::RLEElement.new(chunk: nil, run_length: 2),
111
100
  ]
112
101
 
113
- rle.decode(data)
114
- # => ["foo", "foo", "foo", "bar", nil, nil, 1, 1, 1, 1]
102
+ RLE.decode(data)
103
+ # => ["foo", "foo", "foo", "bar", nil, nil]
115
104
  ```
116
105
 
117
106
  ## Development
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "decoder_mixin"
4
+
5
+ module RunLengthEncodingRb
6
+ #
7
+ # Decode run-length encoded data.
8
+ #
9
+ class Decoder
10
+ include DecoderMixin
11
+
12
+ #
13
+ # Decode data.
14
+ #
15
+ # @param [Array<::RLEElement, #chunk, #run_length>] data
16
+ # Data to decode.
17
+ #
18
+ # @return [Array<Object>]
19
+ # Decoded data.
20
+ #
21
+ def decode(data)
22
+ _raise_on_unsupported_type(data.class) unless data.is_a?(Array)
23
+
24
+ _decode(data)
25
+ end
26
+
27
+ private
28
+
29
+ #
30
+ # @param [Array<::RLEElement, #chunk, #run_length>] data
31
+ #
32
+ # @return [Array]
33
+ #
34
+ def _decode(data)
35
+ decoded_data = []
36
+
37
+ data.each do |e|
38
+ # @todo: catch and try to fix an exception,
39
+ # e.g.: ignore the element, etc.
40
+ _inspect_element(e)
41
+
42
+ e.run_length.times { decoded_data.append(e.chunk) }
43
+ end
44
+
45
+ decoded_data
46
+ end
47
+
48
+ #
49
+ # Raise an error to notify that data isn't an array.
50
+ #
51
+ # @param [Class] klass
52
+ #
53
+ # @raise [::TypeError]
54
+ #
55
+ def _raise_on_unsupported_type(klass)
56
+ raise(
57
+ RunLengthEncodingRb::TypeError,
58
+ "wrong argument type #{klass} (expected Array)"
59
+ )
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunLengthEncodingRb
4
+ #
5
+ # Provides methods to validate #decode input data.
6
+ #
7
+ module DecoderMixin
8
+ EXPECTED_ATTRIBUTES = %w[run_length chunk].freeze
9
+
10
+ private
11
+
12
+ #
13
+ # Ensure that the obj complies with the following requirements:
14
+ # 1. has instant variables:
15
+ # - obj.chunk;
16
+ # - obj.run_length;
17
+ # 2. instant variables are accessible (i.e. has the attr_reader
18
+ # or the attr_accessor);
19
+ # 3. obj.run_length is an Integer;
20
+ # 4. obj.run_length is a positive Integer.
21
+ #
22
+ # @param [Object] obj
23
+ # Object to inspect.
24
+ #
25
+ def _inspect_element(obj)
26
+ attrs_defined_status = _get_attrs_defined_status(obj, EXPECTED_ATTRIBUTES)
27
+ attrs_access_status = _get_attr_accessibility_status(obj, EXPECTED_ATTRIBUTES)
28
+
29
+ _ensure_element_has_defined_attrs(obj, attrs_defined_status)
30
+ _ensure_element_has_accessible_attrs(obj, attrs_access_status)
31
+ _ensure_length_int(obj)
32
+ _ensure_length_non_negative(obj)
33
+ end
34
+
35
+ #
36
+ # Ensure that element has defined attributes.
37
+ #
38
+ # @param [Object] obj
39
+ #
40
+ # @param [Hash{ String => Boolean }] attrs_status
41
+ #
42
+ # @raise [::AttrMissingError]
43
+ #
44
+ def _ensure_element_has_defined_attrs(obj, attrs_status)
45
+ return if _all_pass?(attrs_status.values)
46
+
47
+ raise(
48
+ RunLengthEncodingRb::AttrMissingError,
49
+ "the element #{obj} doesn't have the attribute(s): " \
50
+ "#{_get_bad_attributes(attrs_status).join(", ")}"
51
+ )
52
+ end
53
+
54
+ #
55
+ # Ensure that element has accessible attributes.
56
+ #
57
+ # @param [Object] obj
58
+ #
59
+ # @param [Hash{ String => Boolean }] attrs_status
60
+ #
61
+ # @raise [::AttrInaccessibleError]
62
+ #
63
+ def _ensure_element_has_accessible_attrs(obj, attrs_status)
64
+ return if _all_pass?(attrs_status.values)
65
+
66
+ raise(
67
+ RunLengthEncodingRb::AttrInaccessibleError,
68
+ "in the element #{obj}, the following attribute(s) are inaccessible: " \
69
+ "#{_get_bad_attributes(attrs_status).join(", ")} (attr_reader/attr_accessor is missing?)"
70
+ )
71
+ end
72
+
73
+ #
74
+ # Return an array of the attribute names that are
75
+ # missing or inaccessible.
76
+ #
77
+ # @param [Hash{ String => Boolean }] hash
78
+ #
79
+ # @return [Array<String>]
80
+ #
81
+ def _get_bad_attributes(hash)
82
+ hash.select { |_k, v| v == false }.keys
83
+ end
84
+
85
+ #
86
+ # @param [Array<Boolean>] array
87
+ #
88
+ # @return [Boolean]
89
+ #
90
+ def _all_pass?(array)
91
+ array.all? { |e| e == true }
92
+ end
93
+
94
+ #
95
+ # Return the array of missing attributes.
96
+ #
97
+ # @param [Array<Boolean>] attr_accessibility
98
+ # Attributes accessability.
99
+ #
100
+ # @param [Array<String>] attrs_arr
101
+ #
102
+ # @return [Array<String>]
103
+ # Array of attributes that are not accessable.
104
+ #
105
+ def _missing_attributes(attr_accessibility, attrs_arr)
106
+ (
107
+ Hash[
108
+ attrs_arr.zip(attr_accessibility)
109
+ ].delete_if { |_k, v| v == true }
110
+ ).keys
111
+ end
112
+
113
+ #
114
+ # For the object 'obj', get attribute presence status for
115
+ # each attribute name from the 'attrs_arr'.
116
+ #
117
+ # @param [Object] obj
118
+ # Object to inspect.
119
+ #
120
+ # @param [Array<String>] attrs_arr
121
+ # Array of attribute names.
122
+ #
123
+ # @return [Hash{ String => Boolean }]
124
+ #
125
+ def _get_attrs_defined_status(obj, attrs_arr)
126
+ _get_attr_status(obj, attrs_arr) do |obj_to_check, attr_name|
127
+ obj_to_check.instance_variable_defined?("@#{attr_name}")
128
+ end
129
+ end
130
+
131
+ #
132
+ # For the object 'obj', get attribute accessibility status for
133
+ # each attribute from the 'attrs_arr'.
134
+ #
135
+ # @param [Object] obj
136
+ # Object to inspect.
137
+ #
138
+ # @param [Array<String>] attrs_arr
139
+ # Array of attribute names.
140
+ #
141
+ # @return [Hash{ String => Boolean }]
142
+ #
143
+ def _get_attr_accessibility_status(obj, attrs_arr)
144
+ _get_attr_status(obj, attrs_arr) do |obj_to_check, attr_name|
145
+ obj_to_check.respond_to?(attr_name)
146
+ end
147
+ end
148
+
149
+ #
150
+ # Abstract method to get statuses (true/false) for the elements
151
+ # of the 'attrs_arr' input array. The actual check with a boolean
152
+ # response should be handled by the block.
153
+ #
154
+ # @param [Object] obj
155
+ #
156
+ # @param [Array<String>] attrs_arr
157
+ #
158
+ # @yeld [obj_to_check, attr_name]
159
+ # Performs actual check for an element 'e'.
160
+ #
161
+ # @yieldparam [Object] obj_to_check
162
+ #
163
+ # @yieldparam [String] attr_name
164
+ #
165
+ # @yieldreturn [Boolean]
166
+ #
167
+ # @return [Hash{ String => Boolean }]
168
+ #
169
+ def _get_attr_status(obj, attrs_arr, &_block)
170
+ statuses_arr = attrs_arr.map { |e| yield(obj, e) }
171
+ Hash[attrs_arr.zip(statuses_arr)]
172
+ end
173
+
174
+ #
175
+ # Ensure that run-length attribute of the element is an Integer.
176
+ #
177
+ # @param [Object] obj
178
+ #
179
+ # @raise [::TypeError]
180
+ #
181
+ def _ensure_length_int(obj)
182
+ return if obj.run_length.is_a?(Integer)
183
+
184
+ raise(
185
+ RunLengthEncodingRb::TypeError,
186
+ "wrong run-length type: #{obj.run_length.class} (expected Integer)"
187
+ )
188
+ end
189
+
190
+ #
191
+ # Ensure that run-length attribute of the element is a non-negative Integer.
192
+ #
193
+ # @param [Object] obj
194
+ #
195
+ # @raise [::NegativeIntError]
196
+ #
197
+ def _ensure_length_non_negative(obj)
198
+ return if obj.run_length >= 0
199
+
200
+ raise(
201
+ RunLengthEncodingRb::NegativeIntError,
202
+ "error in #{obj}: run-length should be a non-negative Integer"
203
+ )
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunLengthEncodingRb
4
+ #
5
+ # Encode data with RLE.
6
+ #
7
+ class Encoder
8
+ #
9
+ # Encode an array with the run-length encoding.
10
+ #
11
+ # @param [Array, String, Enumerator] data
12
+ # Data to encode.
13
+ #
14
+ # @option [String, Regexp] separator ("")
15
+ # Separator for the String-typed data.
16
+ #
17
+ # @return [Array<::RLEElement>]
18
+ # Encoded data.
19
+ #
20
+ def encode(data, separator = "")
21
+ array = _to_array(data, separator)
22
+
23
+ _raise_on_unsupported_type(data.class) unless array
24
+
25
+ _encode(array)
26
+ end
27
+
28
+ private
29
+
30
+ #
31
+ # @param [Array] data
32
+ # Data to encode.
33
+ #
34
+ # @return [Array<::RLEElement>]
35
+ # Encoded data.
36
+ #
37
+ def _encode(data)
38
+ result = []
39
+
40
+ data.each.inject([]) do |memo, element|
41
+ if memo.last && element == memo.last.last
42
+ memo.last[0] += 1
43
+ else
44
+ memo << [1, element]
45
+ end
46
+ result = memo
47
+ end
48
+ result.map { |run_length, chunk| RLEElement.new(chunk: chunk, run_length: run_length) }
49
+ end
50
+
51
+ #
52
+ # Raise an error to notify that input data isn't an array
53
+ # and can't be converted to an array.
54
+ #
55
+ # @param [Class] klass
56
+ #
57
+ # @raise [::TypeError]
58
+ #
59
+ def _raise_on_unsupported_type(klass)
60
+ raise(
61
+ RunLengthEncodingRb::TypeError,
62
+ "wrong argument type #{klass} (expected Array, String or Enumerator)"
63
+ )
64
+ end
65
+
66
+ #
67
+ # Return an array representation of an input data.
68
+ #
69
+ # @param [Object] data
70
+ #
71
+ # @option [String, Regexp]
72
+ #
73
+ # @return [Array, nil] data
74
+ # nil indicates that input data cannot be converted to an array.
75
+ #
76
+ def _to_array(data, separator)
77
+ case data
78
+ when Array
79
+ return data
80
+ when String
81
+ return data.split(separator)
82
+ when Enumerator
83
+ return data.to_a
84
+ end
85
+
86
+ nil
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunLengthEncodingRb
4
+ #
5
+ # Raises if an element has inaccessible attribute
6
+ # (misses attr_reader or attr_accessor).
7
+ #
8
+ class AttrInaccessibleError < Error
9
+ attr_reader :message
10
+
11
+ def initialize(message = "")
12
+ super(message)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunLengthEncodingRb
4
+ #
5
+ # Raises if an element lacks attribute.
6
+ #
7
+ class AttrMissingError < Error
8
+ attr_reader :message
9
+
10
+ def initialize(message = "")
11
+ super(message)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunLengthEncodingRb
4
+ #
5
+ # Raises if run-length is negative.
6
+ #
7
+ class NegativeIntError < Error
8
+ attr_reader :message
9
+
10
+ def initialize(message = "")
11
+ super(message)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunLengthEncodingRb
4
+ #
5
+ # Raises on incorrect data type.
6
+ #
7
+ class TypeError < Error
8
+ attr_reader :message
9
+
10
+ def initialize(message = "")
11
+ super(message)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunLengthEncodingRb
4
+ #
5
+ # Base class.
6
+ #
7
+ class Error < ::StandardError
8
+ attr_reader :message
9
+
10
+ def initialize(message)
11
+ @message = message
12
+ super()
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunLengthEncodingRb
4
+ #
5
+ # Stores an run-length encoded element.
6
+ #
7
+ class RLEElement
8
+ attr_accessor :chunk, :run_length
9
+
10
+ def initialize(chunk:, run_length:)
11
+ @chunk = chunk
12
+ @run_length = run_length
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunLengthEncodingRb
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "run_length_encoding_rb/decoder"
4
+ require_relative "run_length_encoding_rb/encoder"
5
+ require_relative "run_length_encoding_rb/error"
6
+ require_relative "run_length_encoding_rb/error/attr_inaccessible_error"
7
+ require_relative "run_length_encoding_rb/error/attr_missing_error"
8
+ require_relative "run_length_encoding_rb/error/negative_int_error"
9
+ require_relative "run_length_encoding_rb/error/type_error"
10
+ require_relative "run_length_encoding_rb/rle_element"
11
+ require_relative "run_length_encoding_rb/version"
12
+
13
+ #
14
+ # Run-length encoding/decoding.
15
+ #
16
+ module RunLengthEncodingRb
17
+ #
18
+ # Encode data.
19
+ #
20
+ # @param [Array, String, Enumerator] data
21
+ # Data to encode.
22
+ #
23
+ # @option [String, Regexp] separator ("")
24
+ # Separator for the String-typed data.
25
+ #
26
+ # @return [Array<::RLEElement>]
27
+ # Encoded data.
28
+ #
29
+ def self.encode(data, separator = "")
30
+ encoder = Encoder.new
31
+ encoder.encode(data, separator)
32
+ end
33
+
34
+ #
35
+ # Decode data.
36
+ #
37
+ # @param [Array<::RLEElement, #chunk, #run_length>] data
38
+ # Data to decode.
39
+ #
40
+ # @return [Array<Object>]
41
+ # Decoded data.
42
+ #
43
+ def self.decode(data)
44
+ decoder = Decoder.new
45
+ decoder.decode(data)
46
+ end
47
+ end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/run_length_encoding/version"
3
+ require_relative "lib/run_length_encoding_rb/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "run_length_encoding_rb"
7
- spec.version = RunLengthEncoding::VERSION
7
+ spec.version = RunLengthEncodingRb::VERSION
8
8
  spec.authors = ["8bit-mate"]
9
9
 
10
10
  spec.summary = "Run-length encoding/decoding."
11
11
  spec.homepage = "https://github.com/8bit-mate/run_length_encoding.rb"
12
12
  spec.license = "MIT"
13
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
14
14
 
15
15
  spec.metadata["allowed_push_host"] = "https://rubygems.org/"
16
16
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: run_length_encoding_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 8bit-mate
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-24 00:00:00.000000000 Z
11
+ date: 2023-10-21 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -26,12 +26,17 @@ files:
26
26
  - Rakefile
27
27
  - bin/console
28
28
  - bin/setup
29
- - lib/run_length_encoding.rb
30
- - lib/run_length_encoding/constants.rb
31
- - lib/run_length_encoding/decoder.rb
32
- - lib/run_length_encoding/decoding_element_check.rb
33
- - lib/run_length_encoding/encoder.rb
34
- - lib/run_length_encoding/version.rb
29
+ - lib/run_length_encoding_rb.rb
30
+ - lib/run_length_encoding_rb/decoder.rb
31
+ - lib/run_length_encoding_rb/decoder_mixin.rb
32
+ - lib/run_length_encoding_rb/encoder.rb
33
+ - lib/run_length_encoding_rb/error.rb
34
+ - lib/run_length_encoding_rb/error/attr_inaccessible_error.rb
35
+ - lib/run_length_encoding_rb/error/attr_missing_error.rb
36
+ - lib/run_length_encoding_rb/error/negative_int_error.rb
37
+ - lib/run_length_encoding_rb/error/type_error.rb
38
+ - lib/run_length_encoding_rb/rle_element.rb
39
+ - lib/run_length_encoding_rb/version.rb
35
40
  - run_length_encoding.gemspec
36
41
  homepage: https://github.com/8bit-mate/run_length_encoding.rb
37
42
  licenses:
@@ -49,14 +54,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
54
  requirements:
50
55
  - - ">="
51
56
  - !ruby/object:Gem::Version
52
- version: 2.3.0
57
+ version: 3.0.0
53
58
  required_rubygems_version: !ruby/object:Gem::Requirement
54
59
  requirements:
55
60
  - - ">="
56
61
  - !ruby/object:Gem::Version
57
62
  version: '0'
58
63
  requirements: []
59
- rubygems_version: 3.2.3
64
+ rubygems_version: 3.4.10
60
65
  signing_key:
61
66
  specification_version: 4
62
67
  summary: Run-length encoding/decoding.
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Constants
4
- CHUNK_KEY = :chunk
5
- COUNT_KEY = :count
6
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "decoding_element_check"
4
- require_relative "constants"
5
-
6
- #
7
- # Decode run-length encoded data.
8
- #
9
- module Decoder
10
- include Constants
11
- include DecodingElementCheck
12
-
13
- #
14
- # Pass data to the decoder.
15
- #
16
- # @param [Array<Hash{ Symbol => Object }>] data
17
- #
18
- # @return [Array]
19
- #
20
- # @raise [ArgumentError, TypeError]
21
- # Raises if data isn't an Array [TypeError] or an empty Array [ArgumentError].
22
- #
23
- def decode(data)
24
- ex_details = _check_dec_data(data)
25
-
26
- raise(ex_details[:ex_type], ex_details[:ex_msg]) if ex_details
27
-
28
- _decode(data)
29
- end
30
-
31
- private
32
-
33
- def _check_dec_data(data)
34
- case data
35
- when Array
36
- if data.to_a.empty?
37
- {
38
- ex_type: ArgumentError,
39
- ex_msg: "the given data is empty (no data to decode)"
40
- }
41
- end
42
- else
43
- {
44
- ex_type: TypeError,
45
- ex_msg: "wrong argument type #{data.class} (expected Array)"
46
- }
47
- end
48
- end
49
-
50
- #
51
- # Decode data.
52
- #
53
- # @param [Array<Hash{ Symbol => Object }>] data
54
- #
55
- # @return [Array]
56
- #
57
- # @raise [ArgumentError, TypeError]
58
- #
59
- def _decode(data)
60
- decoded_data = []
61
-
62
- data.each do |hash|
63
- check_result = _valid_hash?(hash)
64
- raise(check_result[:ex_type], check_result[:ex_msg]) unless check_result[:comply]
65
-
66
- hash[COUNT_KEY].times { decoded_data.append(hash[CHUNK_KEY]) }
67
- end
68
-
69
- decoded_data
70
- end
71
- end
@@ -1,108 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "constants"
4
-
5
- #
6
- # Set of checks of a single element to decode.
7
- #
8
- module DecodingElementCheck
9
- include Constants
10
-
11
- private
12
-
13
- #
14
- # Check if element obj complies with the following requirements:
15
- # 1. is a hash;
16
- # 2. has keys:
17
- # - Constants::CHUNK_KEY (repeating element),
18
- # - Constants::COUNT_KEY (how many times element is repeating);
19
- # 3. COUNT_KEY's value is an Integer;
20
- # 4. COUNT_KEY's value is a positive number.
21
- #
22
- # @param [Object] obj
23
- # Object to check.
24
- #
25
- # @return [Hash{ Symbol => Object }] result
26
- # Result of checks.
27
- # If all checks are passed:
28
- # result = { comply: true }
29
- #
30
- # If a check is failed, result also contains exception type and exception message:
31
- # result = { comply: false, ex_type: Exception, ex_msg: String }
32
- #
33
- def _valid_hash?(obj)
34
- check_list = [
35
- method(:_a_hash?),
36
- method(:_valid_keys?),
37
- method(:_count_is_integer?),
38
- method(:_count_is_positive?)
39
- ]
40
-
41
- for method in check_list
42
- result = method.call(obj)
43
- break unless result[:comply]
44
- end
45
-
46
- result
47
- end
48
-
49
- # Check if obj is a hash.
50
- def _a_hash?(obj)
51
- if obj.is_a?(Hash)
52
- {
53
- comply: true
54
- }
55
- else
56
- {
57
- ex_type: TypeError,
58
- ex_msg: "wrong argument type #{obj.class} (expected Hash)",
59
- comply: false
60
- }
61
- end
62
- end
63
-
64
- # Check if hash has valid keys.
65
- def _valid_keys?(hash)
66
- if hash.key?(CHUNK_KEY) && hash.key?(COUNT_KEY)
67
- {
68
- comply: true
69
- }
70
- else
71
- {
72
- ex_type: ArgumentError,
73
- ex_msg: "hash #{hash} has wrong keys (key is missing or has a wrong name)",
74
- comply: false
75
- }
76
- end
77
- end
78
-
79
- # Check if count is an Integer.
80
- def _count_is_integer?(hash)
81
- if hash[COUNT_KEY].is_a?(Integer)
82
- {
83
- comply: true
84
- }
85
- else
86
- {
87
- ex_type: TypeError,
88
- ex_msg: "wrong argument type #{hash[COUNT_KEY].class} (expected Integer)",
89
- comply: false
90
- }
91
- end
92
- end
93
-
94
- # Check if count is a positive number.
95
- def _count_is_positive?(hash)
96
- if hash[COUNT_KEY].positive?
97
- {
98
- comply: true
99
- }
100
- else
101
- {
102
- ex_type: ArgumentError,
103
- ex_msg: "error in #{hash}: count should be a positive Integer",
104
- comply: false
105
- }
106
- end
107
- end
108
- end
@@ -1,104 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "constants"
4
-
5
- #
6
- # Encode data with RLE.
7
- #
8
- module Encoder
9
- include Constants
10
-
11
- #
12
- # Check and then pass data to the actual encoder.
13
- #
14
- # @param [Array, String, Enumerator] data
15
- # Data to encode.
16
- #
17
- # @param [String, Regexp] separator ("")
18
- # Separator for the string arguments.
19
- #
20
- # @return [Array<Hash{ Symbol => Object }>]
21
- # Encoded data. Each element is a Hash with the following key-value pairs:
22
- # [Constants::CHUNK_KEY => Object]
23
- # A repeated object.
24
- #
25
- # [Constants::COUNT_KEY => Integer]
26
- # How many times the object is repeated.
27
- #
28
- # @raise [ArgumentError, TypeError]
29
- # Raises if data isn't an Array/String/Enumerator [TypeError] or an empty Array [ArgumentError].
30
- #
31
- def encode(data, separator = "")
32
- valid_data, ex_details = _check_enc_data(data, separator)
33
-
34
- raise(ex_details[:ex_type], ex_details[:ex_msg]) if ex_details
35
-
36
- _encode(valid_data)
37
- end
38
-
39
- private
40
-
41
- #
42
- # Check if data is an array, convert to an array if possible.
43
- #
44
- # @return [Array] data
45
- # Ready-to-be-encoded data.
46
- #
47
- # @return [Hash{ Symbol => Object }] ex_details
48
- # Exception type and error if data can't be encoded.
49
- #
50
- def _check_enc_data(data, separator)
51
- ex_details = nil
52
-
53
- case data
54
- when Array
55
- array = data
56
- when String
57
- array = data.split(separator)
58
- when Enumerator
59
- array = data.to_a
60
- else
61
- ex_details = {
62
- ex_type: TypeError,
63
- ex_msg: "wrong argument type #{data.class} (expected Array, String or Enumerator)"
64
- }
65
- end
66
-
67
- if array.to_a.empty?
68
- ex_details = {
69
- ex_type: ArgumentError,
70
- ex_msg: "the given data is empty (no data to encode)"
71
- }
72
- end
73
-
74
- return array, ex_details
75
- end
76
-
77
- #
78
- # Encode array using run-length encoding.
79
- #
80
- # @param [Array] data
81
- # Data to encode.
82
- #
83
- # @return [Array<Hash{ Symbol => Object }>]
84
- # Encoded data. Each element is a Hash with the following key-value pairs:
85
- # [Constants::CHUNK_KEY => Object]
86
- # A repeated object.
87
- #
88
- # [Constants::COUNT_KEY => Integer]
89
- # How many times the object is repeated.
90
- #
91
- def _encode(data)
92
- result = []
93
-
94
- data.each.inject([]) do |memo, element|
95
- if memo.last && element == memo.last.last
96
- memo.last[0] += 1
97
- else
98
- memo << [1, element]
99
- end
100
- result = memo
101
- end
102
- result.map { |count, chunk| { CHUNK_KEY => chunk, COUNT_KEY => count } }
103
- end
104
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class RunLengthEncoding
4
- VERSION = "0.2.1"
5
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "run_length_encoding/version"
4
- require_relative "run_length_encoding/decoder"
5
- require_relative "run_length_encoding/encoder"
6
-
7
- #
8
- # Provides methods to encode and decode data using RLE.
9
- #
10
- class RunLengthEncoding
11
- include Encoder
12
- include Decoder
13
-
14
- def initialize(*); end
15
- end