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 +4 -4
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +2 -15
- data/Gemfile.lock +2 -2
- data/README.md +31 -42
- data/lib/run_length_encoding_rb/decoder.rb +62 -0
- data/lib/run_length_encoding_rb/decoder_mixin.rb +206 -0
- data/lib/run_length_encoding_rb/encoder.rb +89 -0
- data/lib/run_length_encoding_rb/error/attr_inaccessible_error.rb +15 -0
- data/lib/run_length_encoding_rb/error/attr_missing_error.rb +14 -0
- data/lib/run_length_encoding_rb/error/negative_int_error.rb +14 -0
- data/lib/run_length_encoding_rb/error/type_error.rb +14 -0
- data/lib/run_length_encoding_rb/error.rb +15 -0
- data/lib/run_length_encoding_rb/rle_element.rb +15 -0
- data/lib/run_length_encoding_rb/version.rb +5 -0
- data/lib/run_length_encoding_rb.rb +47 -0
- data/run_length_encoding.gemspec +3 -3
- metadata +15 -10
- data/lib/run_length_encoding/constants.rb +0 -6
- data/lib/run_length_encoding/decoder.rb +0 -71
- data/lib/run_length_encoding/decoding_element_check.rb +0 -108
- data/lib/run_length_encoding/encoder.rb +0 -104
- data/lib/run_length_encoding/version.rb +0 -5
- data/lib/run_length_encoding.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58c5fa25ae5085cbc17f1107dcf7df5c7ac13014613df72bb9c235fbadb56a22
|
4
|
+
data.tar.gz: c4406fd2bccb4b6942764cc8baa581c9bcbfad7b6d54562afbaac6a88b652f6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c5fd9a1be1c71c03d7c2b50a68469f76240acb5997d26d0700f5eb23e8f159f413609ced27abe1b7522c3dd2368bb699ee75980891efb150ca1d64cc8132da4
|
7
|
+
data.tar.gz: d7c3e0783c080bd4d1d8df55c5bc305d17fd233281d1694feb30b88aebe29538f95068a575e828d50a48a9780853b60946355575383f2b990fe2aa9d77c3d534
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,16 +1,3 @@
|
|
1
|
-
### 0.
|
1
|
+
### 1.0.0 / 2023-10-21
|
2
2
|
|
3
|
-
*
|
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
data/README.md
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
+
# run_length_encoding_rb [](https://badge.fury.io/rb/run_length_encoding_rb)
|
2
|
+
|
1
3
|
## Description
|
2
4
|
|
3
|
-
|
5
|
+
Run-length encoding for Ruby.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
7
|
-
Add this line to your application
|
9
|
+
Add this line to your application"s Gemfile:
|
8
10
|
|
9
11
|
```ruby
|
10
|
-
gem
|
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
|
-
|
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
|
44
|
+
+ _Array\<RunLengthEncodingRb::RLEElement\>_
|
43
45
|
|
44
|
-
Encoded data
|
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
|
51
|
+
require "run_length_encoding_rb"
|
53
52
|
|
54
|
-
|
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
|
-
|
60
|
-
#
|
61
|
-
|
62
|
-
|
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
|
68
|
-
str =
|
69
|
-
|
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 =
|
74
|
-
|
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
|
-
|
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
|
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
|
92
|
+
require "run_length_encoding_rb"
|
102
93
|
|
103
|
-
|
94
|
+
RLE = RunLengthEncodingRb
|
104
95
|
|
105
|
-
# Decode data of mixed types:
|
106
96
|
data = [
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
114
|
-
# => ["foo", "foo", "foo", "bar", nil, nil
|
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,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,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
|
data/run_length_encoding.gemspec
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "lib/
|
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 =
|
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(">=
|
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.
|
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-
|
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/
|
30
|
-
- lib/
|
31
|
-
- lib/
|
32
|
-
- lib/
|
33
|
-
- lib/
|
34
|
-
- lib/
|
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:
|
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.
|
64
|
+
rubygems_version: 3.4.10
|
60
65
|
signing_key:
|
61
66
|
specification_version: 4
|
62
67
|
summary: Run-length encoding/decoding.
|
@@ -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
|
data/lib/run_length_encoding.rb
DELETED
@@ -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
|