run_length_encoding_rb 0.2.1 → 1.0.0

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: 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