Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
33 / 33
Converter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
2 / 2
12
100.00% covered (success)
100.00%
33 / 33
 createAlphabet
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
11 / 11
 convert
100.00% covered (success)
100.00%
1 / 1
8
100.00% covered (success)
100.00%
22 / 22
1<?php
2
3namespace TonyBogdanov\Alphabase;
4
5use TonyBogdanov\Alphabase\Exception\InvalidAlphabetException;
6use TonyBogdanov\Alphabase\Exception\InvalidInputException;
7
8/**
9 * Class Converter
10 *
11 * @package TonyBogdanov\Alphabase
12 */
13class Converter {
14
15    /** @var array[] */
16    protected static array $alphabets = [];
17
18    /**
19     * @param string $symbols
20     * @return array
21     * @throws InvalidAlphabetException
22     */
23    protected static function createAlphabet( string $symbols ): array {
24
25        if ( ! isset( static::$alphabets[ $symbols ] ) ) {
26
27            $alphabet = str_split( $symbols );
28            $unique = array_unique( $alphabet );
29
30            if ( count( $unique ) !== count( $alphabet ) ) {
31                throw new InvalidAlphabetException( 'The alphabet must contain only unique characters.' );
32            }
33
34            if ( 2 > count( $unique ) ) {
35                throw new InvalidAlphabetException( sprintf(
36                    'The alphabet must contain at least 2 characters, got: %d.',
37                    count( $unique ),
38                ) );
39            }
40
41            static::$alphabets[ $symbols ] = [ $unique, array_flip( $unique ) ];
42
43        }
44
45        return static::$alphabets[ $symbols ];
46
47    }
48
49    /**
50     * @param string $input
51     * @param string $inputAlphabet
52     * @param string $outputAlphabet
53     * @return string
54     * @throws InvalidAlphabetException
55     * @throws InvalidInputException
56     */
57    public static function convert( string $input, string $inputAlphabet, string $outputAlphabet ): string {
58
59        if ( '' === $input ) {
60            return '';
61        }
62
63        [ $is, $isf ] = static::createAlphabet( $inputAlphabet );
64        [ $os ] = static::createAlphabet( $outputAlphabet );
65
66        $invalid = array_diff( array_unique( str_split( $input ) ), $is );
67        if ( 0 < count( $invalid ) ) {
68            throw new InvalidInputException( sprintf(
69                'The input string contains characters outside of the input alphabet: %s.',
70                json_encode( array_values( $invalid ) ),
71            ) );
72        }
73
74        $ib = count( $is );
75        $ob = count( $os );
76
77        $carry = preg_match( '/^' . preg_quote( $is[0], '/' ) . '+/', $input, $match ) ? strlen( $match[0] ) : 0;
78        if ( $carry === strlen( $input ) ) {
79            return str_repeat( $os[0], $carry );
80        }
81
82        $value = '0';
83        foreach ( array_reverse( str_split( substr( $input, $carry ) ) ) as $index => $symbol ) {
84            $value = bcadd( $value, bcmul( $isf[ $symbol ], bcpow( $ib, $index ) ) );
85        }
86
87        $result = [];
88        while ( -1 === bccomp( 0, $value ) ) {
89            array_unshift( $result, $os[ (int) bcmod( $value, $ob ) ] );
90            $value = bcdiv( $value, $ob );
91        }
92
93        return ( 0 < $carry ? str_repeat( $os[0], $carry ) : '' ) . implode( '', $result );
94
95    }
96
97}