Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
Dictionary
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
6 / 6
26
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 generate
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 fill
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 set
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
8
 get
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getEntry
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
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 */
13declare(strict_types=1);
14
15namespace 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 */
25final 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}