Issues (12)

src/StringCombinations.php (1 issue)

1
<?php
2
3
namespace BenTools\StringCombinations;
4
5
use function BenTools\CartesianProduct\cartesian_product;
6
use Countable;
7
use IteratorAggregate;
8
use Traversable;
9
10
/**
11
 * @property $min
12
 * @property $max
13
 * @property $charset
14
 * @property $glue
15
 */
16
final class StringCombinations implements IteratorAggregate, Countable
17
{
18
    /**
19
     * @var string[]
20
     */
21
    private array $charset;
22
23
    private int $min;
24
25
    private int $max;
26
27
    private ?int $count = null;
28
29
    private string $glue;
30
31
    public function __construct($charset, int $min = 1, ?int $max = null, string $glue = '')
32
    {
33
        if (is_string($charset) || is_int($charset)) {
34
            $this->charset = preg_split('/(?<!^)(?!$)/u', $charset);
35
            $this->validateCharset($this->charset);
36
        } elseif (is_array($charset)) {
37
            $this->charset = $charset;
38
            $this->validateCharset($this->charset);
39
        } else {
40
            $this->denyCharset();
41
        }
42
        $this->min = $min;
43
        $length = count($this->charset);
44
        $this->max = null === $max ? $length : min((int) $max, $this->charset);
45
        $this->glue = $glue;
46
    }
47
48
    public function withoutDuplicates(): NoDuplicateLettersStringCombinations
49
    {
50
        return new NoDuplicateLettersStringCombinations($this);
51
    }
52
53
    public function count(): int
54
    {
55
        if (null === $this->count) {
56
            $this->count = array_sum(array_map(function ($set) {
57
                return count(cartesian_product($set));
58
            }, iterator_to_array($this->generateSets())));
59
        }
60
        return $this->count;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->count could return the type double which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
61
    }
62
63
    public function getIterator(): Traversable
64
    {
65
        foreach ($this->generateSets() as $set) {
66
            foreach (cartesian_product($set) as $combination) {
67
                yield implode($this->glue, $combination);
68
            }
69
        }
70
    }
71
72
    /**
73
     * Creates a random string from current charset
74
     * @return string
75
     */
76
    public function getRandomString(): string
77
    {
78
        $length = random_int($this->min, $this->max);
79
        $charset = $this->charset;
80
        for ($pos = 0, $str = []; $pos < $length; $pos++) {
81
            shuffle($charset);
82
            $str[] = $charset[0];
83
        }
84
        return implode($this->glue, $str);
85
    }
86
87
    public function asArray(): array
88
    {
89
        return iterator_to_array($this);
90
    }
91
92
    private function generateSets(): \Generator
93
    {
94
        for ($i = $this->min; $i <= $this->max; $i++) {
95
            $set = array_fill(0, $i, $this->charset);
96
            yield $set;
97
        }
98
    }
99
100
    private function validateCharset($charset): void
101
    {
102
        if (null === $charset) {
103
            $this->denyCharset();
104
        }
105
        foreach ($charset as $value) {
106
            if (!is_string($value) && !is_integer($value)) {
107
                $this->denyCharset();
108
            }
109
        }
110
    }
111
112
    /**
113
     * @throws \InvalidArgumentException
114
     */
115
    private function denyCharset()
116
    {
117
        throw new \InvalidArgumentException('Charset should be a string or an array of strings.');
118
    }
119
120
    /**
121
     * @inheritDoc
122
     */
123
    public function __get($name)
124
    {
125
        return $this->{$name};
126
    }
127
}
128