NumberBase::hasDigit()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Riimu\Kit\BaseConversion;
4
5
use Riimu\Kit\BaseConversion\DigitList\ArrayDigitList;
6
use Riimu\Kit\BaseConversion\DigitList\IntegerDigitList;
7
use Riimu\Kit\BaseConversion\DigitList\StringDigitList;
8
9
/**
10
 * Represents a positional numeral system with a specific number base.
11
 *
12
 * NumberBase provides convenience when dealing numbers that are represented by
13
 * a specific list of digits. NumberBase can interpret numbers presented as
14
 * strings and also provides convenience when creating lists of digits.
15
 *
16
 * @author Riikka Kalliomäki <[email protected]>
17
 * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki
18
 * @license http://opensource.org/licenses/mit-license.php MIT License
19
 */
20
class NumberBase
21
{
22
    /** @var DigitList\DigitList List of digits */
23
    private $digits;
24
25
    /** @var string|int|false Pattern for splitting strings into digits */
26
    private $digitPattern;
27
28
    /**
29
     * Creates a new instance of NumberBase.
30
     *
31
     * The constructor takes a list of digits for the numeral system as the
32
     * constructor parameter. This can either be an instance of DigitList or
33
     * it can be a string, an integer or an array that is used to construct
34
     * the appropriate type of DigitList. See the constructors for appropriate
35
     * classes for how to define those digit lists.
36
     *
37
     * @param DigitList\DigitList|int|string|array $digitList List of digits
38
     * @throws \InvalidArgumentException If the list of digits is invalid
39
     */
40 309
    public function __construct($digitList)
41
    {
42 301
        $this->digits = $digitList instanceof DigitList\DigitList
43 309
            ? $digitList : $this->buildDigitList($digitList);
44 285
    }
45
46
    /**
47
     * Returns an appropriate type of digit list based on the parameter.
48
     * @param int|string|array $digitList List of digits
49
     * @return IntegerDigitList|StringDigitList|ArrayDigitList The built digit list
50
     */
51 309
    private function buildDigitList($digitList)
52
    {
53 309
        if (is_int($digitList)) {
54 237
            return new IntegerDigitList($digitList);
55 87
        } elseif (is_string($digitList)) {
56 54
            return new StringDigitList($digitList);
57 48
        } elseif (is_array($digitList)) {
58 45
            return new ArrayDigitList($digitList);
59
        }
60
61 3
        throw new \InvalidArgumentException('Unexpected number base type');
62
    }
63
64
    /**
65
     * Tells if numbers using this numeral system cannot be represented using a string.
66
     * @return bool True if string representation is not supported, false if it is
67
     */
68 129
    public function hasStringConflict()
69
    {
70 129
        return $this->digits->hasStringConflict();
71
    }
72
73
    /**
74
     * Tells if this numeral system is case sensitive or not.
75
     * @return bool True if case sensitive, false if not
76
     */
77 6
    public function isCaseSensitive()
78
    {
79 6
        return $this->digits->isCaseSensitive();
80
    }
81
82
    /**
83
     * Returns the radix (i.e. base) of the numeral system.
84
     * @return int Radix of the numeral system
85
     */
86 216
    public function getRadix()
87
    {
88 216
        return count($this->digits);
89
    }
90
91
    /**
92
     * Returns list of all digits in the numeral system.
93
     * @return array Array of digits in the numeral system
94
     */
95 210
    public function getDigitList()
96
    {
97 210
        return $this->digits->getDigits();
98
    }
99
100
    /**
101
     * Tells if the given digit is part of this numeral system.
102
     * @param mixed $digit The digit to look up
103
     * @return bool True if the digit exists, false is not
104
     */
105 3
    public function hasDigit($digit)
106
    {
107
        try {
108 3
            $this->digits->getValue($digit);
109 3
        } catch (DigitList\InvalidDigitException $ex) {
110 3
            return false;
111
        }
112
113 3
        return true;
114
    }
115
116
    /**
117
     * Returns the decimal value represented by the given digit.
118
     * @param mixed $digit The digit to look up
119
     * @return int The decimal value for the provided digit
120
     * @throws DigitList\InvalidDigitException If the given digit is invalid
121
     */
122 33
    public function getValue($digit)
123
    {
124 33
        return $this->digits->getValue($digit);
125
    }
126
127
    /**
128
     * Returns the decimal values for given digits.
129
     * @param array $digits Array of digits to look up
130
     * @return int[] Array of digit values
131
     * @throws DigitList\InvalidDigitException If any of the digits is invalid
132
     */
133 204
    public function getValues(array $digits)
134
    {
135 204
        $values = [];
136
137 204
        foreach ($digits as $digit) {
138 204
            $values[] = $this->digits->getValue($digit);
139 64
        }
140
141 192
        return $values;
142
    }
143
144
    /**
145
     * Returns the digit representing the given decimal value.
146
     * @param int $decimal Decimal value to lookup
147
     * @return mixed The digit that represents the given decimal value
148
     * @throws \InvalidArgumentException If the decimal value is not within the number system
149
     */
150 165
    public function getDigit($decimal)
151
    {
152 165
        return $this->digits->getDigit($decimal);
153
    }
154
155
    /**
156
     * Returns the digits representing the given decimal values.
157
     * @param int[] $decimals Decimal values to look up
158
     * @return array Array of digits that represent the given decimal values
159
     * @throws \InvalidArgumentException If any of the decimal values is invalid
160
     */
161 192
    public function getDigits(array $decimals)
162
    {
163 192
        $digits = [];
164
165 192
        foreach ($decimals as $decimal) {
166 192
            $digits[] = $this->digits->getDigit($decimal);
167 64
        }
168
169 192
        return $digits;
170
    }
171
172
    /**
173
     * Finds the largest integer root shared by the radix of both numeral systems.
174
     * @param NumberBase $base Numeral system to compare against
175
     * @return int|false Highest common integer root or false if none
176
     */
177 129
    public function findCommonRadixRoot(NumberBase $base)
178
    {
179 129
        $common = array_intersect($this->getRadixRoots(), $base->getRadixRoots());
180
181 129
        return count($common) > 0 ? max($common) : false;
182
    }
183
184
    /**
185
     * Returns all integer roots for the radix.
186
     * @return int[] Array of integer roots for the radix
187
     */
188 129
    private function getRadixRoots()
189
    {
190 129
        $radix = count($this->digits);
191 129
        $roots = [$radix];
192
193 129
        for ($i = 2; ($root = (int) ($radix ** (1 / $i))) > 1; $i++) {
194 123
            if ($root ** $i === $radix) {
195 105
                $roots[] = $root;
196 35
            }
197 41
        }
198
199 129
        return $roots;
200
    }
201
202
    /**
203
     * Replaces all values in the array with actual digits from the digit list.
204
     *
205
     * This method takes a list of digits and returns the digits properly
206
     * capitalized and typed. This can be used to canonize numbers when dealing
207
     * with case insensitive and loosely typed number bases.
208
     *
209
     * @param array $digits List of digits to canonize
210
     * @return array Canonized list of digits
211
     * @throws DigitList\InvalidDigitException If any of the digits are invalid
212
     */
213 195
    public function canonizeDigits(array $digits)
214
    {
215 195
        $result = $this->getDigits($this->getValues($digits));
216
217 183
        return empty($result) ? [$this->digits->getDigit(0)] : $result;
218
    }
219
220
    /**
221
     * Splits number string into individual digits.
222
     * @param string $string String to split into array of digits
223
     * @return array Array of digits
224
     * @throws \RuntimeException If numeral system does not support strings
225
     */
226 105
    public function splitString($string)
227
    {
228 105
        if ($this->digits->hasStringConflict()) {
229 3
            throw new \RuntimeException('The number base does not support string presentation');
230
        }
231
232 102
        $pattern = $this->getDigitPattern();
233
234 102
        if ((string) $string === '') {
235 6
            $digits = [];
236 102
        } elseif (is_int($pattern)) {
237 99
            $digits = str_split($string, $this->digitPattern);
238 33
        } else {
239 6
            preg_match_all($pattern, $string, $match);
240 6
            $digits = $match[0];
241
        }
242
243 102
        return $this->canonizeDigits($digits);
244
    }
245
246
    /**
247
     * Creates and returns the pattern for splitting strings into digits.
248
     * @return string|int Pattern to split strings into digits
249
     */
250 102
    private function getDigitPattern()
251
    {
252 102
        if (!isset($this->digitPattern)) {
253 102
            $lengths = array_map('strlen', $this->digits->getDigits());
254
255 102
            if (count(array_flip($lengths)) === 1) {
256 99
                $this->digitPattern = array_pop($lengths);
257 33
            } else {
258 6
                $this->digitPattern = sprintf(
259 6
                    '(%s|.+)s%s',
260 6
                    implode('|', array_map('preg_quote', $this->digits->getDigits())),
261 6
                    $this->digits->isCaseSensitive() ? '' : 'i'
262 2
                );
263
            }
264 34
        }
265
266 102
        return $this->digitPattern;
267
    }
268
}
269