ArrayDigitList::__construct()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 12
cts 12
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 10
nc 4
nop 1
crap 3
1
<?php
2
3
namespace Riimu\Kit\BaseConversion\DigitList;
4
5
/**
6
 * Handles a list of digits provided as an array.
7
 * @author Riikka Kalliomäki <[email protected]>
8
 * @copyright Copyright (c) 2015-2017 Riikka Kalliomäki
9
 * @license http://opensource.org/licenses/mit-license.php MIT License
10
 */
11
class ArrayDigitList extends AbstractDigitList
12
{
13
    /**
14
     * Creates a new instance of ArrayDigitList.
15
     *
16
     * The list of digits is provided as an array. The index provides value for
17
     * the digits and the values provide the digits themselves. Any kind of
18
     * value is an acceptable digit, but note that the digits are considered
19
     * duplicate if their values are equal using a loose comparison.
20
     *
21
     * @param array $digits The list of digits for the numeral system
22
     * @throws \InvalidArgumentException If the list of digits is invalid
23
     */
24 45
    public function __construct(array $digits)
25
    {
26 45
        $this->validateDigits($digits);
27
28 33
        $this->digits = $digits;
29
30 33
        $mapped = array_map('strval', array_filter($digits, [$this, 'isMapped']));
31 33
        $this->valueMap = array_flip($mapped);
32 33
        $this->stringConflict = count($mapped) === count($this->digits)
33 33
            ? $this->detectConflict($mapped, 'strpos') : true;
34
35 33
        $this->caseSensitive = $this->detectConflict($mapped, 'stripos');
36
37 33
        if (!$this->caseSensitive) {
38 27
            array_change_key_case($this->valueMap);
39 9
        }
40 33
    }
41
42
    /**
43
     * Validates and sorts the list of digits.
44
     * @param array $digits The list of digits for the numeral system
45
     * @throws \InvalidArgumentException If the digit list is invalid
46
     */
47 45
    private function validateDigits(& $digits)
48
    {
49 45
        ksort($digits);
50
51 45
        if (count($digits) < 2) {
52 3
            throw new \InvalidArgumentException('Number base must have at least 2 digits');
53 42
        } elseif (array_keys($digits) !== range(0, count($digits) - 1)) {
54 3
            throw new \InvalidArgumentException('Invalid digit values in the number base');
55 39
        } elseif ($this->detectDuplicates($digits)) {
56 6
            throw new \InvalidArgumentException('Number base cannot have duplicate digits');
57
        }
58 33
    }
59
60
    /**
61
     * Tells if the list of digits has duplicate values.
62
     * @param array $digits The list of digits for the numeral system
63
     * @return bool True if the list contains duplicate digits, false if not
64
     */
65 39
    private function detectDuplicates(array $digits)
66
    {
67 39
        for ($i = count($digits); $i > 0; $i--) {
68 39
            if (array_search(array_pop($digits), $digits) !== false) {
69 6
                return true;
70
            }
71 12
        }
72
73 33
        return false;
74
    }
75
76
    /**
77
     * Tells if the digit can be mapped using a value map.
78
     * @param mixed $digit The digit to test
79
     * @return bool True if the digit can be mapped, false if not
80
     */
81 33
    private function isMapped($digit)
82
    {
83 33
        return is_string($digit) || is_int($digit);
84
    }
85
86
    /**
87
     * Tells if a conflict exists between string values.
88
     * @param string[] $digits The list of digits for the numeral system
89
     * @param callable $detect Function used to detect the conflict
90
     * @return bool True if a conflict exists, false if not
91
     */
92 33
    private function detectConflict(array $digits, callable $detect)
93
    {
94 33
        foreach ($digits as $digit) {
95 27
            if ($this->inDigits($digit, $digits, $detect)) {
96 23
                return true;
97
            }
98 9
        }
99
100 27
        return false;
101
    }
102
103
    /**
104
     * Tells if a conflict exists for a digit in a list of digits.
105
     * @param string $digit A single digit to test
106
     * @param string[] $digits The list of digits for the numeral system
107
     * @param callable $detect Function used to detect the conflict
108
     * @return bool True if a conflict exists, false if not
109
     */
110 27
    private function inDigits($digit, array $digits, callable $detect)
111
    {
112 27
        foreach ($digits as $haystack) {
113 27
            if ($digit !== $haystack && $detect($haystack, $digit) !== false) {
114 23
                return true;
115
            }
116 9
        }
117
118 21
        return false;
119
    }
120
121 21
    public function getValue($digit)
122
    {
123 21
        if ($this->isMapped($digit)) {
124 15
            return parent::getValue($digit);
125
        }
126
127 6
        $value = array_search($digit, $this->digits);
128
129 6
        if ($value === false) {
130 3
            throw new InvalidDigitException('Invalid digit');
131
        }
132
133 3
        return $value;
134
    }
135
}
136