Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
50 / 50 |
|
100.00% |
6 / 6 |
CRAP | |
100.00% |
1 / 1 |
Dictionary | |
100.00% |
50 / 50 |
|
100.00% |
6 / 6 |
26 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
generate | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
4 | |||
fill | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
set | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
8 | |||
get | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
getEntry | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\Utils\Encoding\Huffman |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\Utils\Encoding\Huffman; |
16 | |
17 | /** |
18 | * Gray encoding class |
19 | * |
20 | * @package phpOMS\Utils\Encoding\Huffman |
21 | * @license OMS License 2.0 |
22 | * @link https://jingga.app |
23 | * @since 1.0.0 |
24 | */ |
25 | final class Dictionary |
26 | { |
27 | /** |
28 | * Huffman dictionary. |
29 | * |
30 | * @var array<string, string> |
31 | * @since 1.0.0 |
32 | */ |
33 | private array $dictionary = []; |
34 | |
35 | /** |
36 | * Minimum length. |
37 | * |
38 | * @var int |
39 | * @since 1.0.0 |
40 | */ |
41 | private int $min = -1; |
42 | |
43 | /** |
44 | * Maximum length. |
45 | * |
46 | * @var int |
47 | * @since 1.0.0 |
48 | */ |
49 | private int $max = -1; |
50 | |
51 | /** |
52 | * Constructor. |
53 | * |
54 | * @param string $source Source to create the dictionary from |
55 | * |
56 | * @since 1.0.0 |
57 | */ |
58 | public function __construct(string $source = '') |
59 | { |
60 | if (!empty($source)) { |
61 | $this->generate($source); |
62 | } |
63 | } |
64 | |
65 | /** |
66 | * Generate dictionary from data. |
67 | * |
68 | * @param string $source Source data to generate dictionary from |
69 | * |
70 | * @return void |
71 | * |
72 | * @since 1.0.0 |
73 | */ |
74 | public function generate(string $source) : void |
75 | { |
76 | $this->dictionary = []; |
77 | $this->min = -1; |
78 | $this->max = -1; |
79 | |
80 | /** @var array<int, array<int, string|array>> $count */ |
81 | $count = []; |
82 | while (isset($source[0])) { |
83 | $count[] = [\substr_count($source, $source[0]), $source[0]]; |
84 | $source = \str_replace($source[0], '', $source); |
85 | } |
86 | |
87 | \sort($count); |
88 | while (\count($count) > 1) { |
89 | $row1 = \array_shift($count); |
90 | $row2 = \array_shift($count); |
91 | $count[] = [$row1[0] + $row2[0], [$row1, $row2]]; |
92 | |
93 | \sort($count); |
94 | } |
95 | |
96 | $this->fill(\is_array($count[0][1]) ? $count[0][1] : $count); |
97 | } |
98 | |
99 | /** |
100 | * Fill dictionary. |
101 | * |
102 | * @param array<int, array<int, string|array>> $entry Source data to generate dictionary from |
103 | * @param string $value Dictionary value |
104 | * |
105 | * @return void |
106 | * |
107 | * @since 1.0.0 |
108 | */ |
109 | private function fill(array $entry, string $value = '') : void |
110 | { |
111 | if (!\is_array($entry[0][1])) { |
112 | $this->set($entry[0][1], $value . '0'); |
113 | } else { |
114 | $this->fill($entry[0][1], $value . '0'); |
115 | } |
116 | |
117 | if (isset($entry[1])) { |
118 | if (!\is_array($entry[1][1])) { |
119 | $this->set($entry[1][1], $value . '1'); |
120 | } else { |
121 | $this->fill($entry[1][1], $value . '1'); |
122 | } |
123 | } |
124 | } |
125 | |
126 | /** |
127 | * Set dictionary value |
128 | * |
129 | * @param string $entry 1 character entry |
130 | * @param string $value Dictionary value |
131 | * |
132 | * @return void |
133 | * |
134 | * @throws \InvalidArgumentException |
135 | * |
136 | * @since 1.0.0 |
137 | */ |
138 | public function set(string $entry, string $value) : void |
139 | { |
140 | if (\strlen($entry) !== 1) { |
141 | throw new \InvalidArgumentException('Must be a character.'); |
142 | } |
143 | |
144 | if (isset($this->dictionary[$entry])) { |
145 | throw new \InvalidArgumentException('Character already exists'); |
146 | } |
147 | |
148 | if (\strlen(\str_replace('0', '', \str_replace('1', '', $value))) !== 0) { |
149 | throw new \InvalidArgumentException('Bad formatting.'); |
150 | } |
151 | |
152 | $length = \strlen($value); |
153 | |
154 | if ($this->min === -1 || $length < $this->min) { |
155 | $this->min = $length; |
156 | } |
157 | |
158 | if ($this->max === -1 || $length > $this->max) { |
159 | $this->max = $length; |
160 | } |
161 | |
162 | $this->dictionary[$entry] = $value; |
163 | } |
164 | |
165 | /** |
166 | * Get dictionary value by entry |
167 | * |
168 | * @param string $entry 1 character entry |
169 | * |
170 | * @return string |
171 | * |
172 | * @throws \InvalidArgumentException |
173 | * |
174 | * @since 1.0.0 |
175 | */ |
176 | public function get(string $entry) : string |
177 | { |
178 | if (\strlen($entry) !== 1) { |
179 | throw new \InvalidArgumentException('Must be a character.'); |
180 | } |
181 | |
182 | if (!isset($this->dictionary[$entry])) { |
183 | throw new \InvalidArgumentException('Character does not exist'); |
184 | } |
185 | |
186 | return $this->dictionary[$entry]; |
187 | } |
188 | |
189 | /** |
190 | * Get dictionary entry and reduce value |
191 | * |
192 | * @param string $value Dictionary value |
193 | * |
194 | * @return null|string |
195 | * |
196 | * @since 1.0.0 |
197 | */ |
198 | public function getEntry(&$value) : ?string |
199 | { |
200 | $length = \strlen($value); |
201 | if ($length < $this->min) { |
202 | return null; |
203 | } |
204 | |
205 | for ($i = $this->min; $i <= $this->max; ++$i) { |
206 | $needle = \substr($value, 0, $i); |
207 | |
208 | foreach ($this->dictionary as $key => $val) { |
209 | if ($needle === $val) { |
210 | $value = \substr($value, $i); |
211 | |
212 | return $key; |
213 | } |
214 | } |
215 | } |
216 | |
217 | return null; |
218 | } |
219 | } |