├── renovate.json ├── .phpunit-watcher.yml ├── exercises ├── Numbers │ ├── Numbers.php │ └── Complete │ │ └── NumbersComplete.php ├── LinkedList │ ├── Node.php │ ├── Complete │ │ ├── NodeComplete.php │ │ └── LinkedListComplete.php │ └── LinkedList.php ├── Vowels │ ├── Vowels.php │ └── Complete │ │ └── VowelsComplete.php ├── MaxChar │ ├── MaxChar.php │ └── Complete │ │ └── MaxCharComplete.php ├── Ladder │ ├── Ladder.php │ └── Complete │ │ └── LadderComplete.php ├── Spiral │ ├── Spiral.php │ └── Complete │ │ └── SpiralComplete.php ├── Tree │ ├── Node.php │ ├── Tree.php │ └── Complete │ │ ├── NodeComplete.php │ │ └── TreeComplete.php ├── Pyramid │ ├── Pyramid.php │ └── Complete │ │ └── PyramidComplete.php ├── MaxHeap │ ├── MaxHeap.php │ └── Complete │ │ └── MaxHeapComplete.php ├── Events │ ├── Events.php │ └── Complete │ │ └── EventsComplete.php ├── Capitalize │ ├── Capitalize.php │ └── Complete │ │ └── CapitalizeComplete.php ├── Fibonacci │ ├── Fibonacci.php │ └── Complete │ │ └── FibonacciComplete.php ├── MaxSubArraySum │ ├── MaxSubArraySum.php │ └── Complete │ │ └── MaxSubArraySumComplete.php ├── Palindrome │ ├── Palindrome.php │ └── Complete │ │ └── PalindromeComplete.php ├── ChainResponsabilityPattern │ ├── Complete │ │ ├── UpperCaseFilter.php │ │ ├── MultipleFilter.php │ │ └── MainFilterAbstract.php │ └── exercice.php ├── FizzBuzz │ ├── FizzBuzz.php │ └── Complete │ │ └── FizzBuzzComplete.php ├── Search │ ├── Search.php │ └── Complete │ │ └── SearchComplete.php ├── Stack │ ├── Stack.php │ └── Complete │ │ └── StackComplete.php ├── QueueFromStacks │ ├── QueueFromStacks.php │ └── Complete │ │ └── QueueFromStacksComplete.php ├── Sort │ ├── Sort.php │ └── Complete │ │ └── SortComplete.php ├── ArrayGroup │ ├── ArrayGroup.php │ └── Complete │ │ └── ArrayGroupComplete.php ├── Anagram │ ├── Anagram.php │ └── Complete │ │ └── AnagramComplete.php ├── Reverse │ ├── Reverse.php │ └── Complete │ │ └── ReverseComplete.php ├── Queue │ ├── Queue.php │ └── Complete │ │ └── QueueComplete.php └── BinarySearchTree │ ├── BinarySearchTree.php │ └── Complete │ └── BinarySearchTreeComplete.php ├── .travis.yml ├── tests ├── FizzBuzz │ ├── Complete │ │ └── FizzBuzzCompleteTest.php │ └── FizzBuzzTest.php ├── Numbers │ ├── Complete │ │ └── NumbersCompleteTest.php │ └── NumbersTest.php ├── GetReflectionMethod.php ├── LinkedList │ ├── NodeTest.php │ ├── Complete │ │ └── NodeCompleteTest.php │ └── LinkedListTest.php ├── Anagram │ ├── Complete │ │ └── AnagramCompleteTest.php │ └── AnagramTest.php ├── MaxSubArraySum │ ├── MaxSubArraySumTest.php │ └── Complete │ │ └── MaxSubArrayCompleteTest.php ├── Capitalize │ ├── CapitalizeTest.php │ └── Complete │ │ └── CapitalizeCompleteTest.php ├── MaxChar │ ├── MaxCharTest.php │ └── Complete │ │ └── MaxCharCompleteTest.php ├── Spiral │ ├── Complete │ │ └── SpiralCompleteTest.php │ └── SpiralTest.php ├── Ladder │ ├── LadderTest.php │ └── Complete │ │ └── LadderCompleteTest.php ├── Pyramid │ ├── PyramidTest.php │ └── Complete │ │ └── PyramidCompleteTest.php ├── ArrayGroup │ ├── ArrayGroupTest.php │ └── Complete │ │ └── ArrayGroupCompleteTest.php ├── Vowels │ ├── VowelsTest.php │ └── Complete │ │ └── VowelsCompleteTest.php ├── Fibonacci │ ├── FibonacciTest.php │ └── Complete │ │ └── FibonacciCompleteTest.php ├── Search │ ├── SearchTest.php │ └── Complete │ │ └── SearchCompleteTest.php ├── ChainResponsabilityPattern │ └── Complete │ │ ├── UpperCaseFilterTest.php │ │ ├── MultipleFilterTest.php │ │ └── ChainResponsabilityPatternTest.php ├── Palindrome │ ├── PalindromeTest.php │ └── Complete │ │ └── PalindromeCompleteTest.php ├── Reverse │ ├── ReverseTest.php │ └── Complete │ │ └── ReverseCompleteTest.php ├── Stack │ ├── Complete │ │ └── StackCompleteTest.php │ └── StackTest.php ├── MaxHeap │ ├── Complete │ │ └── MaxHeapCompleteTest.php │ └── MaxHeapTest.php ├── QueueFromStacks │ ├── QueueFromStacksTest.php │ └── Complete │ │ └── QueueFromStacksCompleteTest.php ├── Tree │ ├── Complete │ │ ├── NodeCompleteTest.php │ │ └── TreeCompleteTest.php │ ├── TreeTest.php │ └── NodeTest.php ├── Queue │ ├── Complete │ │ └── QueueCompleteTest.php │ └── QueueTest.php ├── Sort │ ├── SortTest.php │ └── Complete │ │ └── SortCompleteTest.php ├── BinarySearchTree │ ├── BinarySearchTreeTest.php │ └── Complete │ │ └── BinarySearchTreeCompleteTest.php └── Events │ ├── Complete │ └── EventsCompleteTest.php │ └── EventsTest.php ├── phpunit.xml ├── composer.json ├── LICENSE ├── .all-contributorsrc ├── .gitignore └── README.md /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "automerge": true 4 | } 5 | -------------------------------------------------------------------------------- /.phpunit-watcher.yml: -------------------------------------------------------------------------------- 1 | watch: 2 | directories: 3 | - exercises 4 | - tests 5 | fileMask: '*.php' 6 | -------------------------------------------------------------------------------- /exercises/Numbers/Numbers.php: -------------------------------------------------------------------------------- 1 | '# ' 12 | * '## ' 13 | * '###' 14 | */ 15 | final class Ladder 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /exercises/Spiral/Spiral.php: -------------------------------------------------------------------------------- 1 | ' # ' 12 | * ' ### ' 13 | * '#####' 14 | */ 15 | final class Pyramid 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /exercises/MaxHeap/MaxHeap.php: -------------------------------------------------------------------------------- 1 | 1, 2, 'fizz', 4, 'buzz' 15 | */ 16 | final class FizzBuzz 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /exercises/LinkedList/Complete/NodeComplete.php: -------------------------------------------------------------------------------- 1 | data = $data; 18 | $this->next = $next; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/FizzBuzz/Complete/FizzBuzzCompleteTest.php: -------------------------------------------------------------------------------- 1 | expectOutputString('12fizz4buzzfizz78fizzbuzz11fizz1314fizzbuzz'); 15 | FizzBuzzComplete::print(15); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /exercises/Search/Search.php: -------------------------------------------------------------------------------- 1 | push(1); 17 | * $queue->push(2); 18 | * $queue->peek() === 1; 19 | * $queue->pop() === 1; 20 | */ 21 | final class Stack 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /exercises/Numbers/Complete/NumbersComplete.php: -------------------------------------------------------------------------------- 1 | getMethod($name); 18 | $method->setAccessible(true); 19 | 20 | return $method; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /exercises/Sort/Sort.php: -------------------------------------------------------------------------------- 1 | [[ 1, 2], [3, 4], [5]] 13 | * @example ArrayGroup::group([1, 2, 3, 4, 5], 3) -> [[ 1, 2, 3], [4, 5]] 14 | * @example ArrayGroup::group([1, 2, 3, 4, 5], 6) -> [[ 1, 2, 3, 4, 5]] 15 | */ 16 | final class ArrayGroup 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /exercises/FizzBuzz/Complete/FizzBuzzComplete.php: -------------------------------------------------------------------------------- 1 | data); 18 | self::assertInstanceOf(Node::class, $tail->next); 19 | self::assertSame(2, $tail->next->data); 20 | self::assertNull($tail->next->next); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | ./tests 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/LinkedList/Complete/NodeCompleteTest.php: -------------------------------------------------------------------------------- 1 | data); 17 | self::assertInstanceOf(NodeComplete::class, $tail->next); 18 | self::assertSame(2, $tail->next->data); 19 | self::assertNull($tail->next->next); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /exercises/Reverse/Reverse.php: -------------------------------------------------------------------------------- 1 | expectOutputString('12fizz4buzzfizz78fizzbuzz11fizz1314fizzbuzz'); 26 | FizzBuzz::print(15); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /exercises/Stack/Complete/StackComplete.php: -------------------------------------------------------------------------------- 1 | queue[] = $value; 20 | } 21 | 22 | /** @return mixed|null */ 23 | public function pop() 24 | { 25 | return array_pop($this->queue); 26 | } 27 | 28 | /** @return mixed|null */ 29 | public function peek() 30 | { 31 | return count($this->queue) 32 | ? $this->queue[array_key_last($this->queue)] 33 | : null; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /exercises/Anagram/Complete/AnagramComplete.php: -------------------------------------------------------------------------------- 1 | add(1); 19 | * $queue1->add(2); 20 | * $queue1->add(3); 21 | * $queue1->peek() === 3; 22 | * $queue1->remove() === 3; 23 | * 24 | * $queue2 = new Queue(); 25 | * 26 | * $queue1->add('a'); 27 | * $queue1->add('b'); 28 | * $queue1->add('c'); 29 | * 30 | * Queue::zip(queue1, queue2) -> [1, 'a', 2, 'b', 'c'] 31 | */ 32 | final class Queue 33 | { 34 | } 35 | -------------------------------------------------------------------------------- /exercises/Tree/Tree.php: -------------------------------------------------------------------------------- 1 | data = $data; 21 | } 22 | 23 | /** @param mixed $data */ 24 | public function add($data): void 25 | { 26 | $this->children[] = new self($data); 27 | } 28 | 29 | /** @param mixed $data */ 30 | public function remove($data): void 31 | { 32 | $this->children = array_values(array_filter( 33 | $this->children, 34 | static function ($child) use ($data) { 35 | return $child->data !== $data; 36 | } 37 | )); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Capitalize/CapitalizeTest.php: -------------------------------------------------------------------------------- 1 | */ 12 | private $events = []; 13 | 14 | public function on(string $event, callable $callback): void 15 | { 16 | if (array_key_exists($event, $this->events)) { 17 | $this->events[$event][] = $callback; 18 | } else { 19 | $this->events[$event] = [$callback]; 20 | } 21 | } 22 | 23 | public function fire(string $event): void 24 | { 25 | if (array_key_exists($event, $this->events)) { 26 | /** @var callable $callback */ 27 | foreach ($this->events[$event] as $callback) { 28 | $callback(); 29 | } 30 | } 31 | } 32 | 33 | public function off(string $event): void 34 | { 35 | unset($this->events[$event]); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/MaxChar/MaxCharTest.php: -------------------------------------------------------------------------------- 1 | multiple; 16 | } 17 | 18 | /** 19 | * Set the value of multiple 20 | * 21 | * @return self 22 | */ 23 | public function setMultiple(int $multiple) 24 | { 25 | $this->multiple = $multiple; 26 | 27 | return $this; 28 | } 29 | 30 | /** 31 | * filter integer 32 | * 33 | * @param [type] $content 34 | * @return void 35 | */ 36 | public function doFilter($content = null) 37 | { 38 | $contentFilter = filter_var($content, FILTER_VALIDATE_INT); 39 | 40 | if($contentFilter !== false) 41 | { 42 | return $content * $this->multiple; 43 | } 44 | return $content; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Anagram/AnagramTest.php: -------------------------------------------------------------------------------- 1 | expectOutputString('#'); 26 | Ladder::print(1); 27 | } 28 | 29 | public function testPrintLadder1(): void 30 | { 31 | self::markTestSkipped(); 32 | $this->expectOutputString('# ##'); 33 | Ladder::print(2); 34 | } 35 | 36 | public function testPrintLadder2(): void 37 | { 38 | self::markTestSkipped(); 39 | $this->expectOutputString('# ## ### #### #####'); 40 | Ladder::print(5); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /exercises/ChainResponsabilityPattern/exercice.php: -------------------------------------------------------------------------------- 1 | setMultiple(3); 15 | * $uppercaseFilter->setSuccessor($uppercaseFilter); 16 | * 17 | * echo $mainFilter.filter('kevin schmitt'); KEVIN SCHMITT 18 | * echo $mainFilter.filter('360'); 24 19 | * echo $mainFilter.filter(12); 24 20 | */ 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Anton Ždanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /tests/Pyramid/PyramidTest.php: -------------------------------------------------------------------------------- 1 | expectOutputString('#'); 26 | Pyramid::print(1); 27 | } 28 | 29 | public function testPrintLadder1(): void 30 | { 31 | self::markTestSkipped(); 32 | $this->expectOutputString(' # ###'); 33 | Pyramid::print(2); 34 | } 35 | 36 | public function testPrintLadder2(): void 37 | { 38 | self::markTestSkipped(); 39 | $this->expectOutputString(' # ### ##### ####### #########'); 40 | Pyramid::print(5); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/ArrayGroup/ArrayGroupTest.php: -------------------------------------------------------------------------------- 1 | _successor; 20 | } 21 | 22 | /** 23 | * setter $_successor 24 | * 25 | * @param MainFilterAbstract $succesor 26 | * @return void 27 | */ 28 | public function setSuccessor(MainFilterAbstract $succesor = null) 29 | { 30 | $this->_successor = $succesor; 31 | return $this; 32 | } 33 | 34 | /** 35 | * filter $content 36 | * 37 | * @param [string|int] $content 38 | * @return string|int 39 | */ 40 | public function filter($content) 41 | { 42 | $result = $this->doFilter($content); 43 | 44 | if ($this->_successor) { 45 | $result = $this->_successor->filter($result); 46 | } 47 | 48 | return $result; 49 | } 50 | 51 | abstract public function doFilter(); 52 | } -------------------------------------------------------------------------------- /tests/Vowels/VowelsTest.php: -------------------------------------------------------------------------------- 1 | $freq) { 27 | if ($freq <= $freqMax) { 28 | continue; 29 | } 30 | 31 | $max = $char; 32 | $freqMax = $freq; 33 | } 34 | 35 | return (string) $max; 36 | } 37 | 38 | public static function get1(string $string): string 39 | { 40 | $array = []; 41 | for ($i = 0; $i < strlen($string); ++$i) { 42 | if (isset($array[$string[$i]])) { 43 | ++$array[$string[$i]]; 44 | } else { 45 | $array[$string[$i]] = 1; 46 | } 47 | } 48 | 49 | arsort($array); 50 | $max = array_keys($array)[0]; 51 | 52 | return (string) $max; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Pyramid/Complete/PyramidCompleteTest.php: -------------------------------------------------------------------------------- 1 | expectOutputString('#'); 15 | PyramidComplete::print1(1); 16 | } 17 | 18 | public function testPrintLadder1(): void 19 | { 20 | $this->expectOutputString(' # ###'); 21 | PyramidComplete::print1(2); 22 | } 23 | 24 | public function testPrintLadder2(): void 25 | { 26 | $this->expectOutputString(' # ### ##### ####### #########'); 27 | PyramidComplete::print1(5); 28 | } 29 | 30 | public function testPrintLadderRecursive(): void 31 | { 32 | $this->expectOutputString('#'); 33 | PyramidComplete::print2(1); 34 | } 35 | 36 | public function testPrintLadderRecursive1(): void 37 | { 38 | $this->expectOutputString(' # ###'); 39 | PyramidComplete::print2(2); 40 | } 41 | 42 | public function testPrintLadderRecursive2(): void 43 | { 44 | $this->expectOutputString(' # ### ##### ####### #########'); 45 | PyramidComplete::print2(5); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /exercises/Ladder/Complete/LadderComplete.php: -------------------------------------------------------------------------------- 1 | queue, $value); 21 | } 22 | 23 | /** @return mixed|null */ 24 | public function remove() 25 | { 26 | return array_pop($this->queue); 27 | } 28 | 29 | /** @return mixed|null */ 30 | public function peek() 31 | { 32 | return count($this->queue) 33 | ? $this->queue[array_key_last($this->queue)] 34 | : null; 35 | } 36 | 37 | public static function zip(self $queue1, self $queue2): self 38 | { 39 | $new = new self(); 40 | 41 | while ($queue1->peek() || $queue2->peek()) { 42 | $value1 = $queue1->remove(); 43 | if ($value1) { 44 | $new->add($value1); 45 | } 46 | 47 | $value2 = $queue2->remove(); 48 | if ($value2) { 49 | $new->add($value2); 50 | } 51 | } 52 | 53 | return $new; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Spiral/SpiralTest.php: -------------------------------------------------------------------------------- 1 | */ 12 | private static $cache = []; 13 | 14 | public static function get1(int $index): int 15 | { 16 | $result = []; 17 | 18 | for ($i = 0; $i <= $index; ++$i) { 19 | if ($i < 2) { 20 | $result[] = $i; 21 | } else { 22 | $result[] = $result[$i - 1] + $result[$i - 2]; 23 | } 24 | } 25 | 26 | return $result[$index]; 27 | } 28 | 29 | public static function get2(int $index): int 30 | { 31 | if ($index < 2) { 32 | return $index; 33 | } 34 | 35 | return self::get2($index - 1) + self::get2($index - 2); 36 | } 37 | 38 | public static function get3(int $index): int 39 | { 40 | if (array_key_exists($index, self::$cache)) { 41 | return self::$cache[$index]; 42 | } 43 | 44 | if ($index < 2) { 45 | self::$cache[$index] = $index; 46 | 47 | return $index; 48 | } 49 | 50 | $result = self::get3($index - 1) + self::get3($index - 2); 51 | self::$cache[$index] = $result; 52 | 53 | return $result; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/MaxChar/Complete/MaxCharCompleteTest.php: -------------------------------------------------------------------------------- 1 | > */ 10 | public static function make(int $length): array 11 | { 12 | $results = []; 13 | 14 | $columnStart = 0; 15 | $columnEnd = $length - 1; 16 | $rowStart = 0; 17 | $rowEnd = $length - 1; 18 | $counter = 1; 19 | 20 | while ($columnStart <= $columnEnd && $rowStart <= $rowEnd) { 21 | for ($column = $columnStart; $column <= $columnEnd; ++$column) { 22 | $results[$rowStart][$column] = $counter; 23 | ++$counter; 24 | } 25 | ++$rowStart; 26 | 27 | for ($row = $rowStart; $row <= $rowEnd; ++$row) { 28 | $results[$row][$columnEnd] = $counter; 29 | ++$counter; 30 | } 31 | --$columnEnd; 32 | 33 | for ($column = $columnEnd; $column >= $columnStart; --$column) { 34 | $results[$rowEnd][$column] = $counter; 35 | ++$counter; 36 | } 37 | --$rowEnd; 38 | 39 | for ($row = $rowEnd; $row >= $rowStart; --$row) { 40 | $results[$row][$columnStart] = $counter; 41 | ++$counter; 42 | } 43 | ++$columnStart; 44 | } 45 | 46 | return $results; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/MaxSubArraySum/Complete/MaxSubArrayCompleteTest.php: -------------------------------------------------------------------------------- 1 | $length) { 18 | return 0; 19 | } 20 | 21 | $max = -INF; 22 | 23 | for ($i = 0; $i < $length - $n + 1; ++$i) { 24 | $temp = 0; 25 | for ($j = 0; $j < $n; ++$j) { 26 | $temp += $input[$i + $j]; 27 | } 28 | if ($temp > $max) { 29 | $max = $temp; 30 | } 31 | } 32 | 33 | return $max; 34 | } 35 | 36 | /** @param int[] $input */ 37 | public static function max2(array $input, int $n): int 38 | { 39 | $length = count($input); 40 | if ($length < $n) { 41 | return 0; 42 | } 43 | $maxSum = 0; 44 | $tempSum = 0; 45 | 46 | for ($i = 0; $i < $n; ++$i) { 47 | $maxSum += $input[$i]; 48 | } 49 | 50 | $tempSum = $maxSum; 51 | 52 | for ($i = $n; $i < $length; ++$i) { 53 | $tempSum = ($tempSum - $input[$i - $n] + $input[$i]); 54 | $maxSum = max($maxSum, $tempSum); 55 | } 56 | 57 | return $maxSum; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /exercises/QueueFromStacks/Complete/QueueFromStacksComplete.php: -------------------------------------------------------------------------------- 1 | storage = new StackComplete(); 19 | $this->temporary = new StackComplete(); 20 | } 21 | 22 | /** @param mixed $value */ 23 | public function add($value): void 24 | { 25 | $this->storage->push($value); 26 | } 27 | 28 | /** @return mixed|null */ 29 | public function remove() 30 | { 31 | while ($this->storage->peek()) { 32 | $this->temporary->push($this->storage->pop()); 33 | } 34 | 35 | $item = $this->temporary->pop(); 36 | 37 | while ($this->temporary->peek()) { 38 | $this->storage->push($this->temporary->pop()); 39 | } 40 | 41 | return $item; 42 | } 43 | 44 | /** @return mixed|null */ 45 | public function peek() 46 | { 47 | while ($this->storage->peek()) { 48 | $this->temporary->push($this->storage->pop()); 49 | } 50 | 51 | $item = $this->temporary->peek(); 52 | 53 | while ($this->temporary->peek()) { 54 | $this->storage->push($this->temporary->pop()); 55 | } 56 | 57 | return $item; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /exercises/ArrayGroup/Complete/ArrayGroupComplete.php: -------------------------------------------------------------------------------- 1 | $iValue) { 40 | $temporary[] = $iValue; 41 | 42 | if ($i === $last) { 43 | $grouped[] = $temporary; 44 | } elseif (count($temporary) === $size) { 45 | $grouped[] = $temporary; 46 | $temporary = []; 47 | } 48 | } 49 | 50 | return $grouped; 51 | } 52 | 53 | /** 54 | * @param mixed[] $array 55 | * 56 | * @return mixed[] 57 | */ 58 | public static function group2(array $array, int $size): array 59 | { 60 | return array_chunk($array, $size); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Search/SearchTest.php: -------------------------------------------------------------------------------- 1 | doFilter($content)); 22 | } 23 | 24 | /** 25 | * 26 | * @return array 27 | */ 28 | public function getContentString(): array 29 | { 30 | return [ 31 | ['lowercase'], 32 | ['MixUpperAndLower'], 33 | ['string#/'], 34 | ]; 35 | } 36 | 37 | /** 38 | * Get same value if value is not string 39 | * @dataProvider getCongetInvalidContent 40 | * 41 | * @param mix $content 42 | * @return void 43 | */ 44 | public function testDoFilterWithNoStringData($content): void 45 | { 46 | $upperCaseFilter = new UpperCaseFilter(); 47 | self::assertSame($content, $upperCaseFilter->doFilter($content)); 48 | } 49 | 50 | /** 51 | * 52 | * @return array 53 | */ 54 | public function getCongetInvalidContent(): array 55 | { 56 | return [ 57 | [34], 58 | ['45'] 59 | ]; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /exercises/Pyramid/Complete/PyramidComplete.php: -------------------------------------------------------------------------------- 1 | = $middleLower && $middleUpper >= $column) { 24 | $line .= '#'; 25 | } else { 26 | $line .= ' '; 27 | } 28 | } 29 | echo $line; 30 | } 31 | } 32 | 33 | public static function print2(int $rows, int $row = 0, string $line = ''): void 34 | { 35 | if ($rows === $row) { 36 | return; 37 | } 38 | 39 | $columns = $rows * 2 - 1; 40 | $length = strlen($line); 41 | 42 | if ($length === $columns) { 43 | echo $line; 44 | self::print2($rows, $row + 1); 45 | 46 | return; 47 | } 48 | 49 | $middle = floor($columns / 2); 50 | $middleLower = $middle - $row; 51 | $middleUpper = $middle + $row; 52 | 53 | if ($length >= $middleLower && $middleUpper >= $length) { 54 | $line .= '#'; 55 | } else { 56 | $line .= ' '; 57 | } 58 | 59 | self::print2($rows, $row, $line); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Search/Complete/SearchCompleteTest.php: -------------------------------------------------------------------------------- 1 | setMultiple($multiple); 25 | 26 | self::assertSame($content * $multiple, $multipleFilter->Filter($content)); 27 | } 28 | 29 | /** 30 | * 31 | * @return array 32 | */ 33 | public function getContentNumbers(): array 34 | { 35 | return [ 36 | [99999999999999, 2], 37 | [12, 2], 38 | [0, 0], 39 | ['0', 0], 40 | ]; 41 | } 42 | 43 | /** 44 | * @dataProvider getContentNoNumbers 45 | * 46 | * @param int|double $content 47 | * @return void 48 | */ 49 | public function testDoFilterWithNoNumber($content, int $multiple): void 50 | { 51 | $multipleFilter = new MultipleFilter(); 52 | $multipleFilter->setMultiple($multiple); 53 | 54 | self::assertSame($content, $multipleFilter->Filter($content)); 55 | } 56 | 57 | /** 58 | * 59 | * @return array 60 | */ 61 | public function getContentNoNumbers(): array 62 | { 63 | return [ 64 | ['Test', 4], 65 | [4.5, 0], 66 | ]; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /exercises/Search/Complete/SearchComplete.php: -------------------------------------------------------------------------------- 1 | $v) { 21 | if ($v === $n) { 22 | return $i; 23 | } 24 | } 25 | 26 | return null; 27 | } 28 | 29 | /** @param mixed[] $input */ 30 | public static function binary(array $input, int $n): ?int 31 | { 32 | if (count($input) === 0) { 33 | return null; 34 | } 35 | 36 | $start = 0; 37 | $end = count($input) - 1; 38 | $middle = intdiv($start + $end, 2); 39 | 40 | while ($input[$middle] !== $n && $start <= $end) { 41 | if ($n < $input[$middle]) { 42 | $end = $middle - 1; 43 | } else { 44 | $start = $middle + 1; 45 | } 46 | $middle = intdiv($start + $end, 2); 47 | } 48 | 49 | return $input[$middle] === $n ? $middle : null; 50 | } 51 | 52 | public static function naive(string $input, string $n): int 53 | { 54 | $counter = 0; 55 | 56 | for ($i = 0, $iMax = strlen($input), $nMax = strlen($n); $i < $iMax; ++$i) { 57 | for ($j = 0; $j < $nMax; ++$j) { 58 | if ($n[$j] !== $input[$i + $j]) { 59 | break; 60 | } 61 | if ($j === $nMax - 1) { 62 | ++$counter; 63 | } 64 | } 65 | } 66 | 67 | return $counter; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /exercises/Palindrome/Complete/PalindromeComplete.php: -------------------------------------------------------------------------------- 1 | root; 22 | $queue = []; 23 | 24 | while ($current) { 25 | array_push($queue, ...$current->children); 26 | $callback($current->data); 27 | $current = array_shift($queue); 28 | } 29 | } 30 | 31 | /** @param callable $callback node data will be passed as the only argument */ 32 | public function traverseDepthFirst(callable $callback): void 33 | { 34 | $current = $this->root; 35 | $queue = []; 36 | 37 | while ($current) { 38 | array_unshift($queue, ...$current->children); 39 | $callback($current->data); 40 | $current = array_shift($queue); 41 | } 42 | } 43 | 44 | /** @return int[] each respective tree level width */ 45 | public static function levelWidth(NodeComplete $root): array 46 | { 47 | $level = 0; 48 | $widths = [0]; 49 | 50 | $queue = [$root, self::SENTINEL]; 51 | 52 | while (count($queue) > 1) { 53 | $current = array_shift($queue); 54 | 55 | if ($current === self::SENTINEL) { 56 | $widths[++$level] = 0; 57 | $queue[] = self::SENTINEL; 58 | } else { 59 | array_push($queue, ...$current->children); 60 | ++$widths[$level]; 61 | } 62 | } 63 | 64 | return $widths; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/ArrayGroup/Complete/ArrayGroupCompleteTest.php: -------------------------------------------------------------------------------- 1 | stack = new StackComplete(); 19 | } 20 | 21 | public function testHasMethods(): void 22 | { 23 | self::assertTrue( 24 | method_exists(StackComplete::class, 'push'), 25 | 'Class does not have method push' 26 | ); 27 | self::assertTrue( 28 | method_exists(StackComplete::class, 'pop'), 29 | 'Class does not have method pop' 30 | ); 31 | self::assertTrue( 32 | method_exists(StackComplete::class, 'peek'), 33 | 'Class does not have method peek' 34 | ); 35 | } 36 | 37 | public function testCanCreateObject(): void 38 | { 39 | self::assertIsObject($this->stack); 40 | } 41 | 42 | public function testCanPush(): void 43 | { 44 | $this->stack->push(1); 45 | $this->stack->push(2); 46 | $this->stack->push(3); 47 | self::assertTrue(true); 48 | } 49 | 50 | public function testCanPop(): void 51 | { 52 | $this->stack->push(1); 53 | $this->stack->push(2); 54 | $this->stack->push(3); 55 | self::assertSame(3, $this->stack->pop()); 56 | self::assertSame(2, $this->stack->pop()); 57 | self::assertSame(1, $this->stack->pop()); 58 | self::assertNull($this->stack->pop()); 59 | } 60 | 61 | public function testCanPeek(): void 62 | { 63 | self::assertNull($this->stack->peek()); 64 | $this->stack->push(1); 65 | $this->stack->push(2); 66 | $this->stack->push(3); 67 | 68 | $this->stack->pop(); 69 | $this->stack->push(1); 70 | 71 | self::assertSame(1, $this->stack->peek()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "azdanov", 10 | "name": "Anton Ždanov", 11 | "avatar_url": "https://avatars2.githubusercontent.com/u/6123841?v=4", 12 | "profile": "https://azdanov.js.org/", 13 | "contributions": [ 14 | "code", 15 | "doc", 16 | "test" 17 | ] 18 | }, 19 | { 20 | "login": "Ahmed-Aboud", 21 | "name": "ahmed", 22 | "avatar_url": "https://avatars0.githubusercontent.com/u/25877831?v=4", 23 | "profile": "https://github.com/Ahmed-Aboud", 24 | "contributions": [ 25 | "code", 26 | "test" 27 | ] 28 | }, 29 | { 30 | "login": "tizis", 31 | "name": "Roman Yepanchenko", 32 | "avatar_url": "https://avatars1.githubusercontent.com/u/16865573?v=4", 33 | "profile": "https://github.com/tizis", 34 | "contributions": [ 35 | "bug" 36 | ] 37 | }, 38 | { 39 | "login": "fathom", 40 | "name": "Fathom", 41 | "avatar_url": "https://avatars3.githubusercontent.com/u/13853845?v=4", 42 | "profile": "https://github.com/fathom", 43 | "contributions": [ 44 | "bug", 45 | "code" 46 | ] 47 | }, 48 | { 49 | "login": "Olden", 50 | "name": "Alexander Katynia", 51 | "avatar_url": "https://avatars3.githubusercontent.com/u/546682?v=4", 52 | "profile": "https://github.com/Olden", 53 | "contributions": [ 54 | "bug" 55 | ] 56 | }, 57 | { 58 | "login": "kevin-schmitt", 59 | "name": "kevin-schmitt", 60 | "avatar_url": "https://avatars2.githubusercontent.com/u/10809414?v=4", 61 | "profile": "https://github.com/kevin-schmitt", 62 | "contributions": [ 63 | "code", 64 | "test" 65 | ] 66 | } 67 | ], 68 | "contributorsPerLine": 7, 69 | "projectName": "php-interview-exercises", 70 | "projectOwner": "azdanov", 71 | "repoType": "github", 72 | "repoHost": "https://github.com", 73 | "commitConvention": "none" 74 | } 75 | -------------------------------------------------------------------------------- /tests/Stack/StackTest.php: -------------------------------------------------------------------------------- 1 | stack = new Stack(); 19 | } 20 | 21 | public function testHasMethods(): void 22 | { 23 | self::markTestSkipped(); 24 | self::assertTrue( 25 | method_exists(Stack::class, 'push'), 26 | 'Class does not have method push' 27 | ); 28 | self::assertTrue( 29 | method_exists(Stack::class, 'pop'), 30 | 'Class does not have method pop' 31 | ); 32 | self::assertTrue( 33 | method_exists(Stack::class, 'peek'), 34 | 'Class does not have method peek' 35 | ); 36 | } 37 | 38 | public function testCanCreateObject(): void 39 | { 40 | self::markTestSkipped(); 41 | self::assertIsObject($this->stack); 42 | } 43 | 44 | public function testCanPush(): void 45 | { 46 | self::markTestSkipped(); 47 | $this->stack->push(1); 48 | $this->stack->push(2); 49 | $this->stack->push(3); 50 | self::assertTrue(true); 51 | } 52 | 53 | public function testCanPop(): void 54 | { 55 | self::markTestSkipped(); 56 | $this->stack->push(1); 57 | $this->stack->push(2); 58 | $this->stack->push(3); 59 | self::assertSame(3, $this->stack->pop()); 60 | self::assertSame(2, $this->stack->pop()); 61 | self::assertSame(1, $this->stack->pop()); 62 | self::assertNull($this->stack->pop()); 63 | } 64 | 65 | public function testCanPeek(): void 66 | { 67 | self::markTestSkipped(); 68 | $this->stack->push(1); 69 | $this->stack->push(2); 70 | $this->stack->push(3); 71 | 72 | $this->stack->pop(); 73 | $this->stack->push(1); 74 | 75 | self::assertSame(1, $this->stack->peek()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/MaxHeap/Complete/MaxHeapCompleteTest.php: -------------------------------------------------------------------------------- 1 | heap = new MaxHeapComplete(); 19 | } 20 | 21 | public function testHasMethods(): void 22 | { 23 | self::assertTrue( 24 | method_exists(MaxHeapComplete::class, 'insert'), 25 | 'Class does not have method insert' 26 | ); 27 | self::assertTrue( 28 | method_exists(MaxHeapComplete::class, 'extractMax'), 29 | 'Class does not have method extractMax' 30 | ); 31 | } 32 | 33 | public function testCanInsertValuesInCorrectOrder(): void 34 | { 35 | $this->heap->insert(41); 36 | $this->heap->insert(39); 37 | $this->heap->insert(33); 38 | $this->heap->insert(18); 39 | $this->heap->insert(27); 40 | $this->heap->insert(12); 41 | $this->heap->insert(55); 42 | 43 | /** 44 | * 55 45 | * / \ 46 | * 39 41 47 | * / \ / \ 48 | * 18 27 12 33. 49 | */ 50 | self::assertSame([55, 39, 41, 18, 27, 12, 33], $this->heap->values); 51 | } 52 | 53 | public function testCanExtractMaxAndOrder(): void 54 | { 55 | $this->heap->insert(41); 56 | $this->heap->insert(39); 57 | $this->heap->insert(33); 58 | $this->heap->insert(18); 59 | $this->heap->insert(27); 60 | $this->heap->insert(12); 61 | $this->heap->insert(55); 62 | 63 | self::assertSame(55, $this->heap->extractMax()); 64 | 65 | /** 66 | * 41 67 | * / \ 68 | * 39 33 69 | * / \ / 70 | * 18 27 12. 71 | */ 72 | self::assertSame([41, 39, 33, 18, 27, 12], $this->heap->values); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/MaxHeap/MaxHeapTest.php: -------------------------------------------------------------------------------- 1 | heap = new MaxHeap(); 19 | } 20 | 21 | public function testHasMethods(): void 22 | { 23 | self::markTestSkipped(); 24 | self::assertTrue( 25 | method_exists(MaxHeap::class, 'insert'), 26 | 'Class does not have method insert' 27 | ); 28 | self::assertTrue( 29 | method_exists(MaxHeap::class, 'extractMax'), 30 | 'Class does not have method extractMax' 31 | ); 32 | } 33 | 34 | public function testCanInsertValuesInCorrectOrder(): void 35 | { 36 | self::markTestSkipped(); 37 | $this->heap->insert(41); 38 | $this->heap->insert(39); 39 | $this->heap->insert(33); 40 | $this->heap->insert(18); 41 | $this->heap->insert(27); 42 | $this->heap->insert(12); 43 | $this->heap->insert(55); 44 | 45 | /** 46 | * 55 47 | * / \ 48 | * 39 41 49 | * / \ / \ 50 | * 18 27 12 33. 51 | */ 52 | self::assertSame([55, 39, 41, 18, 27, 12, 33], $this->heap->values); 53 | } 54 | 55 | public function testCanExtractMaxAndOrder(): void 56 | { 57 | self::markTestSkipped(); 58 | $this->heap->insert(41); 59 | $this->heap->insert(39); 60 | $this->heap->insert(33); 61 | $this->heap->insert(18); 62 | $this->heap->insert(27); 63 | $this->heap->insert(12); 64 | $this->heap->insert(55); 65 | 66 | self::assertSame(55, $this->heap->extractMax()); 67 | 68 | /** 69 | * 41 70 | * / \ 71 | * 39 33 72 | * / \ / 73 | * 18 27 12. 74 | */ 75 | self::assertSame([41, 39, 33, 18, 27, 12], $this->heap->values); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/QueueFromStacks/QueueFromStacksTest.php: -------------------------------------------------------------------------------- 1 | queue = new QueueFromStacks(); 19 | } 20 | 21 | public function testHasMethods(): void 22 | { 23 | self::markTestSkipped(); 24 | self::assertTrue( 25 | method_exists(QueueFromStacks::class, 'add'), 26 | 'Class does not have static method add' 27 | ); 28 | self::assertTrue( 29 | method_exists(QueueFromStacks::class, 'remove'), 30 | 'Class does not have static method remove' 31 | ); 32 | self::assertTrue( 33 | method_exists(QueueFromStacks::class, 'peek'), 34 | 'Class does not have static method peek' 35 | ); 36 | } 37 | 38 | public function testCanCreateObject(): void 39 | { 40 | self::markTestSkipped(); 41 | self::assertIsObject($this->queue); 42 | } 43 | 44 | public function testCanAdd(): void 45 | { 46 | self::markTestSkipped(); 47 | $this->queue->add(1); 48 | $this->queue->add(2); 49 | $this->queue->add(3); 50 | 51 | self::assertTrue(true); 52 | } 53 | 54 | public function testCanRemove(): void 55 | { 56 | self::markTestSkipped(); 57 | $this->queue->add(1); 58 | $this->queue->add(2); 59 | $this->queue->add(3); 60 | 61 | self::assertSame(1, $this->queue->remove()); 62 | self::assertSame(2, $this->queue->remove()); 63 | self::assertSame(3, $this->queue->remove()); 64 | } 65 | 66 | public function testCanPeek(): void 67 | { 68 | self::markTestSkipped(); 69 | $this->queue->add(1); 70 | $this->queue->add(2); 71 | $this->queue->add(3); 72 | $this->queue->remove(); 73 | 74 | self::assertSame(2, $this->queue->peek()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/QueueFromStacks/Complete/QueueFromStacksCompleteTest.php: -------------------------------------------------------------------------------- 1 | queue = new QueueFromStacksComplete(); 19 | } 20 | 21 | public function testHasMethods(): void 22 | { 23 | self::assertTrue( 24 | method_exists(QueueFromStacksComplete::class, 'add'), 25 | 'Class does not have method add' 26 | ); 27 | self::assertTrue( 28 | method_exists(QueueFromStacksComplete::class, 'remove'), 29 | 'Class does not have method remove' 30 | ); 31 | self::assertTrue( 32 | method_exists(QueueFromStacksComplete::class, 'peek'), 33 | 'Class does not have method peek' 34 | ); 35 | } 36 | 37 | public function testCanCreateObject(): void 38 | { 39 | self::assertIsObject($this->queue); 40 | } 41 | 42 | public function testCanAdd(): void 43 | { 44 | $this->queue->add(1); 45 | $this->queue->add(2); 46 | $this->queue->add(3); 47 | 48 | self::assertTrue(true); 49 | } 50 | 51 | public function testCanRemove(): void 52 | { 53 | $this->queue->add(1); 54 | $this->queue->add(2); 55 | $this->queue->add(3); 56 | 57 | self::assertSame(1, $this->queue->remove()); 58 | self::assertSame(2, $this->queue->remove()); 59 | self::assertSame(3, $this->queue->remove()); 60 | self::assertNull($this->queue->remove()); 61 | } 62 | 63 | public function testCanPeek(): void 64 | { 65 | self::assertNull($this->queue->peek()); 66 | 67 | $this->queue->add(1); 68 | $this->queue->add(2); 69 | $this->queue->add(3); 70 | 71 | $this->queue->remove(); 72 | $this->queue->add(1); 73 | 74 | self::assertSame(2, $this->queue->peek()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Tree/Complete/NodeCompleteTest.php: -------------------------------------------------------------------------------- 1 | data); 37 | } 38 | 39 | public function testCanAdd(): void 40 | { 41 | $node = new NodeComplete(10); 42 | 43 | $node->add(20); 44 | 45 | self::assertCount(1, $node->children); 46 | self::assertSame(20, $node->children[0]->data); 47 | } 48 | 49 | public function testCanAddMultiple(): void 50 | { 51 | $node = new NodeComplete(10); 52 | 53 | $node->add(20); 54 | $node->add(30); 55 | $node->add(40); 56 | 57 | self::assertCount(3, $node->children); 58 | self::assertSame(40, $node->children[array_key_last($node->children)]->data); 59 | } 60 | 61 | public function testCanRemove(): void 62 | { 63 | $node = new NodeComplete(10); 64 | $node->add(20); 65 | self::assertSame(20, $node->children[0]->data); 66 | 67 | $node->remove(20); 68 | 69 | self::assertCount(0, $node->children); 70 | } 71 | 72 | public function testCanRemoveMultiple(): void 73 | { 74 | $node = new NodeComplete(10); 75 | $node->add(20); 76 | $node->add(30); 77 | $node->add(40); 78 | self::assertSame(20, $node->children[0]->data); 79 | self::assertSame(30, $node->children[1]->data); 80 | self::assertSame(40, $node->children[2]->data); 81 | 82 | $node->remove(20); 83 | $node->remove(40); 84 | 85 | self::assertCount(1, $node->children); 86 | self::assertSame(30, $node->children[0]->data); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/Tree/Complete/TreeCompleteTest.php: -------------------------------------------------------------------------------- 1 | tree = new TreeComplete(); 19 | } 20 | 21 | public function testHasRootProperty(): void 22 | { 23 | self::assertObjectHasAttribute('root', $this->tree); 24 | } 25 | 26 | public function testTraverseBreadthFirst(): void 27 | { 28 | $letters = []; 29 | $this->tree->root = new NodeComplete('1a'); 30 | $this->tree->root->add('2b'); 31 | $this->tree->root->add('2c'); 32 | $this->tree->root->children[0]->add('3d'); 33 | $this->tree->traverseBreadthFirst( 34 | static function ($data) use (&$letters): void { 35 | $letters[] = $data; 36 | } 37 | ); 38 | 39 | self::assertEquals(['1a', '2b', '2c', '3d'], $letters); 40 | } 41 | 42 | public function testTraverseDepthFirst(): void 43 | { 44 | $letters = []; 45 | $this->tree->root = new NodeComplete('1a'); 46 | $this->tree->root->add('2b'); 47 | $this->tree->root->add('2c'); 48 | $this->tree->root->children[0]->add('3d'); 49 | $this->tree->traverseDepthFirst( 50 | static function ($data) use (&$letters): void { 51 | $letters[] = $data; 52 | } 53 | ); 54 | 55 | self::assertEquals(['1a', '2b', '3d', '2c'], $letters); 56 | } 57 | 58 | public function testLevelWidth1(): void 59 | { 60 | $this->tree->root = new NodeComplete(0); 61 | $this->tree->root->add(1); 62 | $this->tree->root->add(2); 63 | $this->tree->root->add(3); 64 | $this->tree->root->children[0]->add(4); 65 | $this->tree->root->children[1]->add(5); 66 | 67 | self::assertEquals([1, 3, 2], TreeComplete::levelWidth($this->tree->root)); 68 | } 69 | 70 | public function testLevelWidth2(): void 71 | { 72 | $this->tree->root = new NodeComplete(0); 73 | $this->tree->root->add(1); 74 | $this->tree->root->children[0]->add(2); 75 | $this->tree->root->children[0]->add(3); 76 | $this->tree->root->children[0]->children[0]->add(4); 77 | 78 | self::assertEquals([1, 1, 2, 1], TreeComplete::levelWidth($this->tree->root)); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/ChainResponsabilityPattern/Complete/ChainResponsabilityPatternTest.php: -------------------------------------------------------------------------------- 1 | setMultiple($multiple); 32 | 33 | $upperCaseFilter->setSuccessor($multipleFilter); 34 | 35 | $resultContent = $this->getContentExcepted($content, $multiple); 36 | self::assertSame($resultContent, $upperCaseFilter->Filter($content)); 37 | } 38 | 39 | /** 40 | * @dataProvider getContent 41 | * function test pattern with UpperCaseFilter from MultipleFilter 42 | * 43 | * @param [type] $content 44 | * @return void 45 | */ 46 | public function testPatternSuccessorMultipleFilter($content) : void 47 | { 48 | // init 49 | $upperCaseFilter = new UpperCaseFilter(); 50 | $multipleFilter = new MultipleFilter(); 51 | 52 | $multipleFilter->setSuccessor($upperCaseFilter); 53 | $resultContent = $this->getContentExcepted($content); 54 | 55 | self::assertSame($resultContent, $upperCaseFilter->Filter($content)); 56 | } 57 | 58 | /** 59 | * Undocumented function 60 | * 61 | * @return void 62 | */ 63 | public function getContent() 64 | { 65 | return [ 66 | ['test'], 67 | ['Test'], 68 | [24], 69 | ['45hello#'], 70 | [0], 71 | ]; 72 | } 73 | 74 | /** 75 | * get excepted result return by pattern filters 76 | * 77 | * @param [type] $content 78 | * @return void 79 | */ 80 | private function getContentExcepted($content, int $multiple = 1) 81 | { 82 | 83 | return (filter_var($content, FILTER_VALIDATE_INT) || $content === 0) ? ($multiple * $content) : strtoupper($content); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 2 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 3 | 4 | # User-specific stuff 5 | .idea/ 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # Generated files 13 | .idea/**/contentModel.xml 14 | 15 | # Sensitive or high-churn files 16 | .idea/**/dataSources/ 17 | .idea/**/dataSources.ids 18 | .idea/**/dataSources.local.xml 19 | .idea/**/sqlDataSources.xml 20 | .idea/**/dynamic.xml 21 | .idea/**/uiDesigner.xml 22 | .idea/**/dbnavigator.xml 23 | 24 | # Gradle 25 | .idea/**/gradle.xml 26 | .idea/**/libraries 27 | 28 | # Gradle and Maven with auto-import 29 | # When using Gradle or Maven with auto-import, you should exclude module files, 30 | # since they will be recreated, and may cause churn. Uncomment if using 31 | # auto-import. 32 | # .idea/modules.xml 33 | # .idea/*.iml 34 | # .idea/modules 35 | 36 | # CMake 37 | cmake-build-*/ 38 | 39 | # Mongo Explorer plugin 40 | .idea/**/mongoSettings.xml 41 | 42 | # File-based project format 43 | *.iws 44 | 45 | # IntelliJ 46 | out/ 47 | 48 | # mpeltonen/sbt-idea plugin 49 | .idea_modules/ 50 | 51 | # JIRA plugin 52 | atlassian-ide-plugin.xml 53 | 54 | # Cursive Clojure plugin 55 | .idea/replstate.xml 56 | 57 | # Crashlytics plugin (for Android Studio and IntelliJ) 58 | com_crashlytics_export_strings.xml 59 | crashlytics.properties 60 | crashlytics-build.properties 61 | fabric.properties 62 | 63 | # Editor-based Rest Client 64 | .idea/httpRequests 65 | 66 | # Android studio 3.1+ serialized cache file 67 | .idea/caches/build_file_checksums.ser 68 | 69 | composer.phar 70 | /vendor/ 71 | 72 | # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control 73 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 74 | # composer.lock 75 | 76 | .vscode/* 77 | !.vscode/settings.json 78 | !.vscode/tasks.json 79 | !.vscode/launch.json 80 | !.vscode/extensions.json 81 | 82 | # Windows thumbnail cache files 83 | Thumbs.db 84 | ehthumbs.db 85 | ehthumbs_vista.db 86 | 87 | # Dump file 88 | *.stackdump 89 | 90 | # Folder config file 91 | [Dd]esktop.ini 92 | 93 | # Recycle Bin used on file shares 94 | $RECYCLE.BIN/ 95 | 96 | # Windows Installer files 97 | *.cab 98 | *.msi 99 | *.msix 100 | *.msm 101 | *.msp 102 | 103 | # Windows shortcuts 104 | *.lnk 105 | .phpunit.result.cache 106 | .DS_Store -------------------------------------------------------------------------------- /exercises/MaxHeap/Complete/MaxHeapComplete.php: -------------------------------------------------------------------------------- 1 | values[] = $value; 20 | 21 | $currentIndex = count($this->values) - 1; 22 | $currentValue = $this->values[$currentIndex]; 23 | 24 | while (true) { 25 | $parentIndex = (int) (floor($currentIndex - 1) / 2); 26 | $parentValue = $this->values[$parentIndex]; 27 | 28 | if ($currentValue <= $parentValue) { 29 | break; 30 | } 31 | 32 | $this->values[$parentIndex] = $currentValue; 33 | $this->values[$currentIndex] = $parentValue; 34 | $currentIndex = $parentIndex; 35 | } 36 | } 37 | 38 | /** @return mixed */ 39 | public function extractMax() 40 | { 41 | $max = $this->values[0]; 42 | 43 | $this->values[0] = array_pop($this->values); 44 | 45 | $currentIndex = 0; 46 | $length = count($this->values); 47 | $currentElement = $this->values[0]; 48 | 49 | while (true) { 50 | /** @var int $leftChildIndex */ 51 | $leftChildIndex = 2 * $currentIndex + 1; 52 | /** @var int $rightChildIndex */ 53 | $rightChildIndex = 2 * $currentIndex + 2; 54 | 55 | $leftChild = null; 56 | $rightChild = null; 57 | $swapIndex = null; 58 | 59 | if ($leftChildIndex < $length) { 60 | $leftChild = $this->values[$leftChildIndex]; 61 | 62 | if ($leftChild > $currentElement) { 63 | $swapIndex = $leftChildIndex; 64 | } 65 | } 66 | 67 | if ($rightChildIndex < $length) { 68 | $rightChild = $this->values[$rightChildIndex]; 69 | 70 | if (($swapIndex === null && $rightChild > $currentElement) || 71 | ($swapIndex !== null && $rightChild > $leftChild)) { 72 | $swapIndex = $rightChildIndex; 73 | } 74 | } 75 | 76 | if ($swapIndex === null) { 77 | break; 78 | } 79 | 80 | $this->values[$currentIndex] = $this->values[$swapIndex]; 81 | $this->values[$swapIndex] = $currentElement; 82 | $currentIndex = $swapIndex; 83 | } 84 | 85 | return $max; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Tree/TreeTest.php: -------------------------------------------------------------------------------- 1 | tree = new Tree(); 19 | } 20 | 21 | public function testHasRootProperty(): void 22 | { 23 | self::markTestSkipped(); 24 | self::assertObjectHasAttribute('root', $this->tree); 25 | } 26 | 27 | public function testTraverseBreadthFirst(): void 28 | { 29 | self::markTestSkipped(); 30 | $letters = []; 31 | $this->tree->root = new Node('1a'); 32 | $this->tree->root->add('2b'); 33 | $this->tree->root->add('2c'); 34 | $this->tree->root->children[0]->add('3d'); 35 | $this->tree->traverseBreadthFirst( 36 | static function ($data) use (&$letters): void { 37 | $letters[] = $data; 38 | } 39 | ); 40 | 41 | self::assertEquals(['1a', '2b', '2c', '3d'], $letters); 42 | } 43 | 44 | public function testTraverseDepthFirst(): void 45 | { 46 | self::markTestSkipped(); 47 | $letters = []; 48 | $this->tree->root = new Node('1a'); 49 | $this->tree->root->add('2b'); 50 | $this->tree->root->add('2c'); 51 | $this->tree->root->children[0]->add('3d'); 52 | $this->tree->traverseDepthFirst( 53 | static function ($data) use (&$letters): void { 54 | $letters[] = $data; 55 | } 56 | ); 57 | 58 | self::assertEquals(['1a', '2b', '3d', '2c'], $letters); 59 | } 60 | 61 | public function testLevelWidth1(): void 62 | { 63 | self::markTestSkipped(); 64 | $this->tree->root = new Node(0); 65 | $this->tree->root->add(1); 66 | $this->tree->root->add(2); 67 | $this->tree->root->add(3); 68 | $this->tree->root->children[0]->add(4); 69 | $this->tree->root->children[1]->add(5); 70 | 71 | self::assertEquals([1, 3, 2], TreeComplete::levelWidth($this->tree->root)); 72 | } 73 | 74 | public function testLevelWidth2(): void 75 | { 76 | self::markTestSkipped(); 77 | $this->tree->root = new Node(0); 78 | $this->tree->root->add(1); 79 | $this->tree->root->children[0]->add(2); 80 | $this->tree->root->children[0]->add(3); 81 | $this->tree->root->children[0]->children[0]->add(4); 82 | 83 | self::assertEquals([1, 1, 2, 1], TreeComplete::levelWidth($this->tree->root)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/Ladder/Complete/LadderCompleteTest.php: -------------------------------------------------------------------------------- 1 | expectedString1 = sprintf('#%s', PHP_EOL); 29 | $this->expectedString2 = sprintf('# %s##%s', PHP_EOL, PHP_EOL); 30 | $this->expectedString5 = sprintf( 31 | '# %s## %s### %s#### %s#####%s', 32 | PHP_EOL, 33 | PHP_EOL, 34 | PHP_EOL, 35 | PHP_EOL, 36 | PHP_EOL 37 | ); 38 | } 39 | 40 | public function testPrint11(): void 41 | { 42 | $this->expectOutputString($this->expectedString1); 43 | LadderComplete::print1(1); 44 | } 45 | 46 | public function testPrint12(): void 47 | { 48 | $this->expectOutputString($this->expectedString2); 49 | LadderComplete::print1(2); 50 | } 51 | 52 | public function testPrint15(): void 53 | { 54 | $this->expectOutputString($this->expectedString5); 55 | LadderComplete::print1(5); 56 | } 57 | 58 | public function testPrint21(): void 59 | { 60 | $this->expectOutputString($this->expectedString1); 61 | LadderComplete::print2(1); 62 | } 63 | 64 | public function testPrint22(): void 65 | { 66 | $this->expectOutputString($this->expectedString2); 67 | LadderComplete::print2(2); 68 | } 69 | 70 | public function testPrint25(): void 71 | { 72 | $this->expectOutputString($this->expectedString5); 73 | LadderComplete::print2(5); 74 | } 75 | 76 | public function testPrint31(): void 77 | { 78 | $this->expectOutputString($this->expectedString1); 79 | LadderComplete::print3(1); 80 | } 81 | 82 | public function testPrint32(): void 83 | { 84 | $this->expectOutputString($this->expectedString2); 85 | LadderComplete::print3(2); 86 | } 87 | 88 | public function testPrint35(): void 89 | { 90 | $this->expectOutputString($this->expectedString5); 91 | LadderComplete::print3(5); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/Tree/NodeTest.php: -------------------------------------------------------------------------------- 1 | data); 40 | } 41 | 42 | public function testCanAdd(): void 43 | { 44 | self::markTestSkipped(); 45 | $node = new Node(10); 46 | 47 | $node->add(20); 48 | 49 | self::assertCount(1, $node->children); 50 | self::assertSame(20, $node->children[0]->data); 51 | } 52 | 53 | public function testCanAddMultiple(): void 54 | { 55 | self::markTestSkipped(); 56 | $node = new Node(10); 57 | 58 | $node->add(20); 59 | $node->add(30); 60 | $node->add(40); 61 | 62 | self::assertCount(3, $node->children); 63 | self::assertSame(40, $node->children[array_key_last($node->children)]->data); 64 | } 65 | 66 | public function testCanRemove(): void 67 | { 68 | self::markTestSkipped(); 69 | $node = new Node(10); 70 | $node->add(20); 71 | self::assertSame(20, $node->children[0]->data); 72 | 73 | $node->remove(20); 74 | 75 | self::assertCount(0, $node->children); 76 | } 77 | 78 | public function testCanRemoveMultiple(): void 79 | { 80 | self::markTestSkipped(); 81 | $node = new Node(10); 82 | $node->add(20); 83 | $node->add(30); 84 | $node->add(40); 85 | self::assertSame(20, $node->children[0]->data); 86 | self::assertSame(30, $node->children[1]->data); 87 | self::assertSame(40, $node->children[2]->data); 88 | 89 | $node->remove(20); 90 | $node->remove(40); 91 | 92 | self::assertCount(1, $node->children); 93 | self::assertSame(30, $node->children[0]->data); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /exercises/Reverse/Complete/ReverseComplete.php: -------------------------------------------------------------------------------- 1 | = 0; $i--) { 55 | $revers .= mb_substr($string, $i, 1); 56 | } 57 | 58 | return $revers; 59 | } 60 | 61 | public static function int(int $number): int 62 | { 63 | /** @see https://wiki.php.net/rfc/combined-comparison-operator */ 64 | $sign = $number <=> 0; 65 | 66 | return $sign * (int) strrev((string) $number); 67 | } 68 | 69 | public static function int2(int $number): int 70 | { 71 | $revers = 0; 72 | 73 | $sign = $number <=> 0; 74 | $number = abs($number); 75 | 76 | $n = (int) log10($number) + 1; 77 | 78 | for ($i = 1; $i <= $n; $i++) { 79 | $pow = pow(10, $n - $i); 80 | $numeral = (int) ($number / $pow); 81 | $number -= $numeral * $pow; 82 | $revers += $numeral * pow(10, $i - 1); 83 | } 84 | 85 | return $revers * $sign; 86 | } 87 | 88 | public static function int3(int $number): int 89 | { 90 | $reverse = 0; 91 | 92 | $sign = $number <=> 0; 93 | $number = abs($number); 94 | 95 | while ($number > 0) { 96 | $lastDigit = $number % 10; 97 | $reverse = ($reverse * 10) + $lastDigit; 98 | $number = (int) ($number / 10); 99 | } 100 | 101 | return $reverse * $sign; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tests/Fibonacci/Complete/FibonacciCompleteTest.php: -------------------------------------------------------------------------------- 1 | queue = new QueueComplete(); 19 | } 20 | 21 | public function testHasMethods(): void 22 | { 23 | self::assertTrue( 24 | method_exists(QueueComplete::class, 'add'), 25 | 'Class does not have method add' 26 | ); 27 | self::assertTrue( 28 | method_exists(QueueComplete::class, 'remove'), 29 | 'Class does not have method remove' 30 | ); 31 | self::assertTrue( 32 | method_exists(QueueComplete::class, 'peek'), 33 | 'Class does not have method peek' 34 | ); 35 | self::assertTrue( 36 | method_exists(QueueComplete::class, 'zip'), 37 | 'Class does not have static method zip' 38 | ); 39 | } 40 | 41 | public function testCanCreateObject(): void 42 | { 43 | self::assertIsObject($this->queue); 44 | } 45 | 46 | public function testCanAdd(): void 47 | { 48 | $this->queue->add(1); 49 | $this->queue->add(2); 50 | $this->queue->add(3); 51 | 52 | self::assertTrue(true); 53 | } 54 | 55 | public function testCanRemove(): void 56 | { 57 | $this->queue->add(1); 58 | $this->queue->add(2); 59 | $this->queue->add(3); 60 | 61 | self::assertSame(1, $this->queue->remove()); 62 | self::assertSame(2, $this->queue->remove()); 63 | self::assertSame(3, $this->queue->remove()); 64 | self::assertNull($this->queue->remove()); 65 | } 66 | 67 | public function testCanPeek(): void 68 | { 69 | self::assertNull($this->queue->peek()); 70 | $this->queue->add(1); 71 | $this->queue->add(2); 72 | $this->queue->add(3); 73 | 74 | $this->queue->remove(); 75 | $this->queue->add(1); 76 | 77 | self::assertSame(2, $this->queue->peek()); 78 | } 79 | 80 | public function testCanZip(): void 81 | { 82 | $queue1 = new QueueComplete(); 83 | $queue2 = new QueueComplete(); 84 | 85 | $queue1->add(1); 86 | $queue1->add(2); 87 | $queue1->add(3); 88 | $queue1->add(4); 89 | $queue2->add('a'); 90 | $queue2->add('b'); 91 | 92 | $queue3 = QueueComplete::zip($queue1, $queue2); 93 | 94 | self::assertSame(1, $queue3->remove()); 95 | self::assertSame('a', $queue3->remove()); 96 | self::assertSame(2, $queue3->remove()); 97 | self::assertSame('b', $queue3->remove()); 98 | self::assertSame(3, $queue3->remove()); 99 | self::assertSame(4, $queue3->remove()); 100 | self::assertNull($queue3->remove()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /tests/Queue/QueueTest.php: -------------------------------------------------------------------------------- 1 | queue = new Queue(); 19 | } 20 | 21 | public function testHasMethods(): void 22 | { 23 | self::markTestSkipped(); 24 | self::assertTrue( 25 | method_exists(Queue::class, 'add'), 26 | 'Class does not have method add' 27 | ); 28 | self::assertTrue( 29 | method_exists(Queue::class, 'remove'), 30 | 'Class does not have method remove' 31 | ); 32 | self::assertTrue( 33 | method_exists(Queue::class, 'peek'), 34 | 'Class does not have method peek' 35 | ); 36 | self::assertTrue( 37 | method_exists(Queue::class, 'zip'), 38 | 'Class does not have static method zip' 39 | ); 40 | } 41 | 42 | public function testCanCreateObject(): void 43 | { 44 | self::markTestSkipped(); 45 | self::assertIsObject($this->queue); 46 | } 47 | 48 | public function testCanAdd(): void 49 | { 50 | self::markTestSkipped(); 51 | $this->queue->add(1); 52 | $this->queue->add(2); 53 | $this->queue->add(3); 54 | 55 | self::assertTrue(true); 56 | } 57 | 58 | public function testCanRemove(): void 59 | { 60 | self::markTestSkipped(); 61 | $this->queue->add(1); 62 | $this->queue->add(2); 63 | $this->queue->add(3); 64 | 65 | self::assertSame(1, $this->queue->remove()); 66 | self::assertSame(2, $this->queue->remove()); 67 | self::assertSame(3, $this->queue->remove()); 68 | } 69 | 70 | public function testCanPeek(): void 71 | { 72 | self::markTestSkipped(); 73 | $this->queue->add(1); 74 | $this->queue->add(2); 75 | $this->queue->add(3); 76 | 77 | $this->queue->remove(); 78 | $this->queue->add(1); 79 | 80 | self::assertSame(2, $this->queue->peek()); 81 | } 82 | 83 | public function testCanZip(): void 84 | { 85 | self::markTestSkipped(); 86 | $queue1 = new Queue(); 87 | $queue2 = new Queue(); 88 | 89 | $queue1->add(1); 90 | $queue1->add(2); 91 | $queue1->add(3); 92 | $queue1->add(4); 93 | $queue2->add('a'); 94 | $queue2->add('b'); 95 | 96 | $queue3 = Queue::zip($queue1, $queue2); 97 | 98 | self::assertSame(1, $queue3->remove()); 99 | self::assertSame('a', $queue3->remove()); 100 | self::assertSame(2, $queue3->remove()); 101 | self::assertSame('b', $queue3->remove()); 102 | self::assertSame(3, $queue3->remove()); 103 | self::assertSame(4, $queue3->remove()); 104 | self::assertNull($queue3->remove()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/Sort/SortTest.php: -------------------------------------------------------------------------------- 1 | sorted, Sort::bubble($this->input)); 58 | } 59 | 60 | public function testSelectionSort(): void 61 | { 62 | self::markTestSkipped(); 63 | self::assertSame($this->sorted, Sort::selection($this->input)); 64 | } 65 | 66 | public function testInsertionSort(): void 67 | { 68 | self::markTestSkipped(); 69 | self::assertSame($this->sorted, Sort::insertion($this->input)); 70 | } 71 | 72 | /** @throws ReflectionException */ 73 | public function testMergerHelper(): void 74 | { 75 | self::markTestSkipped(); 76 | $left = [1, 9]; 77 | $right = [3, 7, 10]; 78 | 79 | $merger = self::getMethod(Sort::class, 'merger'); 80 | 81 | self::assertSame([1, 3, 7, 9, 10], $merger->invoke(null, $left, $right)); 82 | } 83 | 84 | public function testMergeSort(): void 85 | { 86 | self::markTestSkipped(); 87 | self::assertSame($this->sorted, Sort::merge($this->input)); 88 | } 89 | 90 | public function testQuickSort(): void 91 | { 92 | self::markTestSkipped(); 93 | self::assertSame($this->sorted, Sort::quick($this->input)); 94 | } 95 | 96 | public function testRadixSort(): void 97 | { 98 | self::markTestSkipped(); 99 | self::assertSame([ 12, 23, 345, 2345, 5467, 9852 ], Sort::radix([23, 345, 5467, 12, 2345, 9852])); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/Sort/Complete/SortCompleteTest.php: -------------------------------------------------------------------------------- 1 | sorted, SortComplete::bubble($this->input)); 61 | } 62 | 63 | public function testSelectionSort(): void 64 | { 65 | self::assertSame($this->sorted, SortComplete::selection($this->input)); 66 | } 67 | 68 | public function testInsertionSort(): void 69 | { 70 | self::assertSame($this->sorted, SortComplete::insertion($this->input)); 71 | } 72 | 73 | /** @throws ReflectionException */ 74 | public function testMergerHelper(): void 75 | { 76 | $left = [1, 9]; 77 | $right = [3, 7, 10]; 78 | 79 | $merger = self::getMethod(SortComplete::class, 'merger'); 80 | 81 | self::assertSame([1, 3, 7, 9, 10], $merger->invoke(null, $left, $right)); 82 | } 83 | 84 | public function testMergeSort(): void 85 | { 86 | self::assertSame($this->sorted, SortComplete::merge($this->input)); 87 | } 88 | 89 | public function testQuick1Sort(): void 90 | { 91 | self::assertSame($this->sorted, SortComplete::quick1($this->input)); 92 | } 93 | 94 | public function testQuick2Sort(): void 95 | { 96 | SortComplete::quick2($this->input); 97 | self::assertSame($this->sorted, $this->input); 98 | } 99 | 100 | public function testRadixSort(): void 101 | { 102 | self::assertSame([ 12, 23, 345, 2345, 5467, 9852 ], SortComplete::radix([23, 345, 5467, 12, 2345, 9852])); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /tests/BinarySearchTree/BinarySearchTreeTest.php: -------------------------------------------------------------------------------- 1 | insert(5); 40 | $node->insert(15); 41 | $node->insert(20); 42 | 43 | self::assertSame(5, $node->left->data); 44 | self::assertSame(15, $node->right->data); 45 | self::assertSame(20, $node->right->right->data); 46 | } 47 | 48 | public function testContains(): void 49 | { 50 | self::markTestSkipped(); 51 | $node = new BinarySearchTree(10); 52 | 53 | $node->insert(5); 54 | $node->insert(15); 55 | $node->insert(20); 56 | $node->insert(0); 57 | $node->insert(-5); 58 | $node->insert(3); 59 | 60 | $three = $node->left->left->right; 61 | 62 | self::assertSame($three, $node->contains(3)); 63 | } 64 | 65 | /** 66 | * 10 67 | * / \ 68 | * 5 15 69 | * / \ 70 | * 0 20 71 | * / \ 72 | * -5 3. 73 | */ 74 | public function testDepthFirstInOrder(): void 75 | { 76 | self::markTestSkipped(); 77 | $node = new BinarySearchTree(10); 78 | 79 | $node->insert(5); 80 | $node->insert(15); 81 | $node->insert(20); 82 | $node->insert(0); 83 | $node->insert(-5); 84 | $node->insert(3); 85 | 86 | self::assertSame([-5, 0, 3, 5, 10, 15, 20], $node->depthFirstInOrder()); 87 | } 88 | 89 | /** 90 | * 10 91 | * / \ 92 | * 5 15 93 | * / \ 94 | * 0 20 95 | * / \ 96 | * -5 3. 97 | */ 98 | public function testDepthFirstPostOrder(): void 99 | { 100 | self::markTestSkipped(); 101 | $node = new BinarySearchTree(10); 102 | 103 | $node->insert(5); 104 | $node->insert(15); 105 | $node->insert(20); 106 | $node->insert(0); 107 | $node->insert(-5); 108 | $node->insert(3); 109 | 110 | self::assertSame([-5, 3, 0, 5, 20, 15, 10], $node->depthFirstPostOrder()); 111 | } 112 | 113 | /** 114 | * 10 115 | * / \ 116 | * 5 15 117 | * / \ 118 | * 0 20 119 | * / \ 120 | * -5 3. 121 | */ 122 | public function testDepthFirstPreOrder(): void 123 | { 124 | self::markTestSkipped(); 125 | $node = new BinarySearchTree(10); 126 | 127 | $node->insert(5); 128 | $node->insert(15); 129 | $node->insert(20); 130 | $node->insert(0); 131 | $node->insert(-5); 132 | $node->insert(3); 133 | 134 | self::assertSame([10, 5, 0, -5, 3, 15, 20], $node->depthFirstPreOrder()); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/Events/Complete/EventsCompleteTest.php: -------------------------------------------------------------------------------- 1 | on('load', $callback); 44 | $events->fire('load'); 45 | 46 | self::assertEquals(1, $counter); 47 | } 48 | 49 | public function testOnAndFireMultipleCallbacks(): void 50 | { 51 | $counter = 0; 52 | $events = new EventsComplete(); 53 | $callback1 = static function () use (&$counter): void { 54 | ++$counter; 55 | }; 56 | $callback2 = static function () use (&$counter): void { 57 | ++$counter; 58 | }; 59 | 60 | $events->on('load', $callback1); 61 | $events->on('load', $callback2); 62 | $events->fire('load'); 63 | 64 | self::assertEquals(2, $counter); 65 | } 66 | 67 | public function testOnAndFireMultipleTimes(): void 68 | { 69 | $counter = 0; 70 | $events = new EventsComplete(); 71 | $callback1 = static function () use (&$counter): void { 72 | ++$counter; 73 | }; 74 | $callback2 = static function () use (&$counter): void { 75 | ++$counter; 76 | }; 77 | 78 | $events->on('load', $callback1); 79 | $events->on('load', $callback2); 80 | $events->fire('load'); 81 | $events->fire('load'); 82 | $events->fire('load'); 83 | 84 | self::assertEquals(6, $counter); 85 | } 86 | 87 | public function testOnAndFireWithDifferentNames(): void 88 | { 89 | $counter = 0; 90 | $events = new EventsComplete(); 91 | $callback1 = static function () use (&$counter): void { 92 | ++$counter; 93 | }; 94 | $callback2 = static function () use (&$counter): void { 95 | ++$counter; 96 | }; 97 | 98 | $events->on('load', $callback1); 99 | $events->on('visit', $callback2); 100 | $events->on('load', $callback2); 101 | $events->fire('load'); 102 | $events->fire('visit'); 103 | $events->fire('load'); 104 | 105 | self::assertEquals(5, $counter); 106 | } 107 | 108 | public function testOff(): void 109 | { 110 | $counter = 0; 111 | $events = new EventsComplete(); 112 | $callback1 = static function () use (&$counter): void { 113 | ++$counter; 114 | }; 115 | $callback2 = static function () use (&$counter): void { 116 | ++$counter; 117 | }; 118 | 119 | $events->on('load', $callback1); 120 | $events->on('visit', $callback2); 121 | $events->on('load', $callback2); 122 | 123 | $events->off('load'); 124 | 125 | $events->fire('load'); 126 | $events->fire('visit'); 127 | $events->fire('load'); 128 | 129 | self::assertEquals(1, $counter); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /tests/Events/EventsTest.php: -------------------------------------------------------------------------------- 1 | on('load', $callback); 47 | $events->fire('load'); 48 | 49 | self::assertSame(1, $counter); 50 | } 51 | 52 | public function testOnAndFireMultipleCallbacks(): void 53 | { 54 | self::markTestSkipped(); 55 | $counter = 0; 56 | $events = new Events(); 57 | $callback1 = static function () use (&$counter): void { 58 | ++$counter; 59 | }; 60 | $callback2 = static function () use (&$counter): void { 61 | ++$counter; 62 | }; 63 | 64 | $events->on('load', $callback1); 65 | $events->on('load', $callback2); 66 | $events->fire('load'); 67 | 68 | self::assertSame(2, $counter); 69 | } 70 | 71 | public function testOnAndFireMultipleTimes(): void 72 | { 73 | self::markTestSkipped(); 74 | $counter = 0; 75 | $events = new Events(); 76 | $callback1 = static function () use (&$counter): void { 77 | ++$counter; 78 | }; 79 | $callback2 = static function () use (&$counter): void { 80 | ++$counter; 81 | }; 82 | 83 | $events->on('load', $callback1); 84 | $events->on('load', $callback2); 85 | $events->fire('load'); 86 | $events->fire('load'); 87 | $events->fire('load'); 88 | 89 | self::assertSame(6, $counter); 90 | } 91 | 92 | public function testOnAndFireWithDifferentNames(): void 93 | { 94 | self::markTestSkipped(); 95 | $counter = 0; 96 | $events = new Events(); 97 | $callback1 = static function () use (&$counter): void { 98 | ++$counter; 99 | }; 100 | $callback2 = static function () use (&$counter): void { 101 | ++$counter; 102 | }; 103 | 104 | $events->on('load', $callback1); 105 | $events->on('visit', $callback2); 106 | $events->on('load', $callback2); 107 | $events->fire('load'); 108 | $events->fire('visit'); 109 | $events->fire('load'); 110 | 111 | self::assertSame(5, $counter); 112 | } 113 | 114 | public function testOff(): void 115 | { 116 | self::markTestSkipped(); 117 | $counter = 0; 118 | $events = new Events(); 119 | $callback1 = static function () use (&$counter): void { 120 | ++$counter; 121 | }; 122 | $callback2 = static function () use (&$counter): void { 123 | ++$counter; 124 | }; 125 | 126 | $events->on('load', $callback1); 127 | $events->on('visit', $callback2); 128 | $events->on('load', $callback2); 129 | 130 | $events->off('load'); 131 | 132 | $events->fire('load'); 133 | $events->fire('visit'); 134 | $events->fire('load'); 135 | 136 | self::assertSame(1, $counter); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /exercises/BinarySearchTree/Complete/BinarySearchTreeComplete.php: -------------------------------------------------------------------------------- 1 | data = $data; 20 | } 21 | 22 | public static function validate( 23 | self $node, 24 | ?int $min = null, 25 | ?int $max = null 26 | ): bool { 27 | if (($max !== null && $node->data > $max) || 28 | ($min !== null && $node->data < $min)) { 29 | return false; 30 | } 31 | 32 | if (($node->left && !self::validate($node->left, $min, $node->data)) || 33 | ($node->right && !self::validate($node->right, $node->data, $max))) { 34 | return false; 35 | } 36 | 37 | return true; 38 | } 39 | 40 | /** @param mixed $data */ 41 | public function insert($data): void 42 | { 43 | if ($this->left && $data < $this->data) { 44 | $this->left->insert($data); 45 | 46 | return; 47 | } 48 | 49 | if ($this->right && $data > $this->data) { 50 | $this->right->insert($data); 51 | 52 | return; 53 | } 54 | 55 | if ($data < $this->data) { 56 | $this->left = new self($data); 57 | 58 | return; 59 | } 60 | 61 | if ($data > $this->data) { 62 | $this->right = new self($data); 63 | } 64 | } 65 | 66 | /** @param mixed $data */ 67 | public function contains($data): ?self 68 | { 69 | if ($data === $this->data) { 70 | return $this; 71 | } 72 | 73 | if ($this->left && $data < $this->data) { 74 | return $this->left->contains($data); 75 | } 76 | 77 | if ($this->right && $data > $this->data) { 78 | return $this->right->contains($data); 79 | } 80 | 81 | return null; 82 | } 83 | 84 | /** @return mixed[] */ 85 | public function depthFirstInOrder(): array 86 | { 87 | $data = []; 88 | 89 | self::traverseInOrder($this, static function (self $node) use (&$data): void { 90 | $data[] = $node->data; 91 | }); 92 | 93 | return $data; 94 | } 95 | 96 | /** @return mixed[] */ 97 | public function depthFirstPostOrder(): array 98 | { 99 | $data = []; 100 | 101 | self::traversePostOrder($this, static function (self $node) use (&$data): void { 102 | $data[] = $node->data; 103 | }); 104 | 105 | return $data; 106 | } 107 | 108 | /** @return mixed[] */ 109 | public function depthFirstPreOrder(): array 110 | { 111 | $data = []; 112 | 113 | self::traversePreOrder($this, static function (self $node) use (&$data): void { 114 | $data[] = $node->data; 115 | }); 116 | 117 | return $data; 118 | } 119 | 120 | /** @param mixed[] $node */ 121 | private static function traverseInOrder(self $node, callable $cb): void 122 | { 123 | if ($node->left) { 124 | self::traverseInOrder($node->left, $cb); 125 | } 126 | 127 | $cb($node); 128 | 129 | if ($node->right) { 130 | self::traverseInOrder($node->right, $cb); 131 | } 132 | } 133 | 134 | /** @param mixed[] $node */ 135 | private static function traversePostOrder(self $node, callable $cb): void 136 | { 137 | if ($node->left) { 138 | self::traversePostOrder($node->left, $cb); 139 | } 140 | 141 | if ($node->right) { 142 | self::traversePostOrder($node->right, $cb); 143 | } 144 | 145 | $cb($node); 146 | } 147 | 148 | /** @param mixed[] $node */ 149 | private static function traversePreOrder(self $node, callable $cb): void 150 | { 151 | $cb($node); 152 | 153 | if ($node->left) { 154 | self::traversePreOrder($node->left, $cb); 155 | } 156 | 157 | if ($node->right) { 158 | self::traversePreOrder($node->right, $cb); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /tests/BinarySearchTree/Complete/BinarySearchTreeCompleteTest.php: -------------------------------------------------------------------------------- 1 | insert(5); 37 | $node->insert(15); 38 | $node->insert(20); 39 | 40 | self::assertSame(5, $node->left->data); 41 | self::assertSame(15, $node->right->data); 42 | self::assertSame(20, $node->right->right->data); 43 | } 44 | 45 | public function testContains(): void 46 | { 47 | $node = new BinarySearchTreeComplete(10); 48 | 49 | $node->insert(5); 50 | $node->insert(15); 51 | $node->insert(20); 52 | $node->insert(0); 53 | $node->insert(-5); 54 | $node->insert(3); 55 | 56 | $three = $node->left->left->right; 57 | 58 | self::assertSame($three, $node->contains(3)); 59 | } 60 | 61 | public function testValidateAValidTree(): void 62 | { 63 | $node = new BinarySearchTreeComplete(10); 64 | 65 | $node->insert(5); 66 | $node->insert(15); 67 | $node->insert(0); 68 | $node->insert(20); 69 | 70 | self::assertTrue(BinarySearchTreeComplete::validate($node)); 71 | } 72 | 73 | public function testValidateAnInvalidTree(): void 74 | { 75 | $node = new BinarySearchTreeComplete(10); 76 | 77 | $node->insert(5); 78 | $node->insert(15); 79 | $node->insert(0); 80 | $node->insert(20); 81 | $node->left->left->right = new BinarySearchTreeComplete(10); 82 | 83 | self::assertFalse(BinarySearchTreeComplete::validate($node)); 84 | } 85 | 86 | /** 87 | * 10 88 | * / \ 89 | * 5 15 90 | * / \ 91 | * 0 20 92 | * / \ 93 | * -5 3. 94 | */ 95 | public function testDepthFirstInOrder(): void 96 | { 97 | $node = new BinarySearchTreeComplete(10); 98 | 99 | $node->insert(5); 100 | $node->insert(15); 101 | $node->insert(20); 102 | $node->insert(0); 103 | $node->insert(-5); 104 | $node->insert(3); 105 | 106 | self::assertSame([-5, 0, 3, 5, 10, 15, 20], $node->depthFirstInOrder()); 107 | } 108 | 109 | /** 110 | * 10 111 | * / \ 112 | * 5 15 113 | * / \ 114 | * 0 20 115 | * / \ 116 | * -5 3. 117 | */ 118 | public function testDepthFirstPostOrder(): void 119 | { 120 | $node = new BinarySearchTreeComplete(10); 121 | 122 | $node->insert(5); 123 | $node->insert(15); 124 | $node->insert(20); 125 | $node->insert(0); 126 | $node->insert(-5); 127 | $node->insert(3); 128 | 129 | self::assertSame([-5, 3, 0, 5, 20, 15, 10], $node->depthFirstPostOrder()); 130 | } 131 | 132 | /** 133 | * 10 134 | * / \ 135 | * 5 15 136 | * / \ 137 | * 0 20 138 | * / \ 139 | * -5 3. 140 | */ 141 | public function testDepthFirstPreOrder(): void 142 | { 143 | $node = new BinarySearchTreeComplete(10); 144 | 145 | $node->insert(5); 146 | $node->insert(15); 147 | $node->insert(20); 148 | $node->insert(0); 149 | $node->insert(-5); 150 | $node->insert(3); 151 | 152 | self::assertSame([10, 5, 0, -5, 3, 15, 20], $node->depthFirstPreOrder()); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /tests/Palindrome/Complete/PalindromeCompleteTest.php: -------------------------------------------------------------------------------- 1 | head = $a; 41 | * $a->next = $b; 42 | * $b->next = $c; 43 | * $c->next = $a; 44 | * LinkedList::circular($list) === true 45 | */ 46 | public static function circular(self $list): ?Node 47 | { 48 | } 49 | 50 | /** 51 | * Return a node that is located n places from the end. 52 | * 53 | * Do not use size, and assume that n is always less than 54 | * the list length. 55 | */ 56 | public static function fromLast(self $list, int $n): ?Node 57 | { 58 | } 59 | 60 | /** 61 | * Create a new Node from data and assign the node to the head property. 62 | * 63 | * @param mixed $data 64 | */ 65 | public function insertFirst($data): void 66 | { 67 | } 68 | 69 | /** 70 | * Create a new Node from data and assign the node at the end. 71 | * 72 | * @param mixed $data 73 | */ 74 | public function insertLast($data): void 75 | { 76 | } 77 | 78 | /** 79 | * @return int number of nodes in the linked list 80 | */ 81 | public function size(): int 82 | { 83 | } 84 | 85 | /** 86 | * @return Node first node in the linked list 87 | */ 88 | public function getFirst(): Node 89 | { 90 | } 91 | 92 | /** 93 | * @return Node first node in the linked list 94 | */ 95 | public function getLast(): Node 96 | { 97 | } 98 | 99 | /** 100 | * Empty the linked list. 101 | */ 102 | public function clear(): void 103 | { 104 | } 105 | 106 | /** 107 | * Remove first node from the linked list. 108 | */ 109 | public function removeFirst(): void 110 | { 111 | } 112 | 113 | /** 114 | * Remove last node from the linked list. 115 | */ 116 | public function removeLast(): void 117 | { 118 | } 119 | 120 | /** 121 | * @return Node at specified index 122 | */ 123 | public function getAt(int $index): Node 124 | { 125 | } 126 | 127 | /** 128 | * @param int $index to remove node at 129 | */ 130 | public function removeAt(int $index): void 131 | { 132 | } 133 | 134 | /** 135 | * Insert data at position. 136 | * 137 | * @param mixed $data 138 | */ 139 | public function insertAt($data, int $index): void 140 | { 141 | } 142 | 143 | /** 144 | * @param callable $callback to call for each node in linked list 145 | */ 146 | public function forEach(callable $callback): void 147 | { 148 | } 149 | 150 | /** 151 | * Return the current element. 152 | * 153 | * @see https://php.net/manual/en/iterator.current.php 154 | * 155 | * @return mixed can return any type 156 | */ 157 | public function current() 158 | { 159 | } 160 | 161 | /** 162 | * Move forward to next element. 163 | * 164 | * @see https://php.net/manual/en/iterator.next.php 165 | */ 166 | public function next(): void 167 | { 168 | } 169 | 170 | /** 171 | * Return the key of the current element. 172 | * 173 | * @see https://php.net/manual/en/iterator.key.php 174 | * 175 | * @return mixed scalar on success, or null on failure 176 | */ 177 | public function key() 178 | { 179 | } 180 | 181 | /** 182 | * Checks if current position is valid. 183 | * 184 | * @see https://php.net/manual/en/iterator.valid.php 185 | * 186 | * @return bool The return value will be casted to boolean and then evaluated. 187 | * Returns true on success or false on failure. 188 | */ 189 | public function valid(): bool 190 | { 191 | } 192 | 193 | /** 194 | * Rewind the Iterator to the first element. 195 | * 196 | * @see https://php.net/manual/en/iterator.rewind.php 197 | */ 198 | public function rewind(): void 199 | { 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /exercises/LinkedList/Complete/LinkedListComplete.php: -------------------------------------------------------------------------------- 1 | head = null; 19 | } 20 | 21 | public function size(): int 22 | { 23 | $count = 0; 24 | 25 | $current = $this->head; 26 | 27 | while ($current) { 28 | ++$count; 29 | $current = $current->next; 30 | } 31 | 32 | return $count; 33 | } 34 | 35 | public function getFirst(): ?NodeComplete 36 | { 37 | return $this->head; 38 | } 39 | 40 | public function getLast(): ?NodeComplete 41 | { 42 | $current = $this->head; 43 | if ($current === null) { 44 | return $current; 45 | } 46 | 47 | while ($current->next !== null) { 48 | $current = $current->next; 49 | } 50 | 51 | return $current; 52 | } 53 | 54 | public function clear(): void 55 | { 56 | $this->head = null; 57 | } 58 | 59 | public function removeLast(): void 60 | { 61 | $current = $this->head; 62 | if ($current === null) { 63 | return; 64 | } 65 | 66 | $previous = null; 67 | while ($current->next) { 68 | $previous = $current; 69 | $current = $current->next; 70 | } 71 | 72 | $previous->next = null; 73 | } 74 | 75 | public function removeAt(int $index): void 76 | { 77 | $current = $this->head; 78 | 79 | if ($current === null) { 80 | return; 81 | } 82 | 83 | if ($index === 0 || $current->next === null) { 84 | $this->removeFirst(); 85 | 86 | return; 87 | } 88 | 89 | $previous = null; 90 | for ($i = 0; $current->next && $i <= $index; ++$i) { 91 | $previous = $current; 92 | $current = $current->next; 93 | } 94 | 95 | $previous->next = $current->next; 96 | } 97 | 98 | public function removeFirst(): void 99 | { 100 | if ($this->head === null) { 101 | return; 102 | } 103 | 104 | $this->head = $this->head->next; 105 | } 106 | 107 | /** @param mixed $data */ 108 | public function insertAt($data, int $index): void 109 | { 110 | $current = $this->head; 111 | if ($index <= 0 || !$current) { 112 | $this->insertFirst($data); 113 | 114 | return; 115 | } 116 | 117 | $previous = null; 118 | for ($i = 0; $current->next && $i < $index; ++$i) { 119 | $previous = $current; 120 | $current = $current->next; 121 | } 122 | 123 | if ($current->next === null) { 124 | $this->insertLast($data); 125 | 126 | return; 127 | } 128 | 129 | $previous->next = new NodeComplete($data, $current); 130 | } 131 | 132 | /** @param mixed $data */ 133 | public function insertFirst($data): void 134 | { 135 | if ($this->head) { 136 | $this->head = new NodeComplete($data, $this->head); 137 | } else { 138 | $this->head = new NodeComplete($data); 139 | } 140 | } 141 | 142 | /** @param mixed $data */ 143 | public function insertLast($data): void 144 | { 145 | $last = $this->getLast(); 146 | $new = new NodeComplete($data); 147 | 148 | if ($last) { 149 | $last->next = $new; 150 | } else { 151 | $this->head = $new; 152 | } 153 | } 154 | 155 | public function forEach(callable $callback): void 156 | { 157 | $current = $this->head; 158 | 159 | $counter = 0; 160 | while ($current) { 161 | $callback($current->data, $counter++); 162 | $current = $current->next; 163 | } 164 | } 165 | 166 | public function current(): NodeComplete 167 | { 168 | return $this->getAt($this->position); 169 | } 170 | 171 | public function getAt(int $index): ?NodeComplete 172 | { 173 | $current = $this->head; 174 | for ($i = 0; $current && $i < $index; ++$i) { 175 | $current = $current->next; 176 | } 177 | 178 | return $current; 179 | } 180 | 181 | public function next(): void 182 | { 183 | ++$this->position; 184 | } 185 | 186 | public function key(): int 187 | { 188 | return $this->position; 189 | } 190 | 191 | public function valid(): bool 192 | { 193 | return isset($this->getAt($this->position)->data); 194 | } 195 | 196 | public function rewind(): void 197 | { 198 | $this->position = 0; 199 | } 200 | 201 | public static function midpoint(self $list): ?NodeComplete 202 | { 203 | $current = $list->head; 204 | if (!$current) { 205 | return $current; 206 | } 207 | 208 | $sentinel = $list->head; 209 | while ($sentinel->next && $sentinel->next->next) { 210 | $current = $current->next; 211 | $sentinel = $sentinel->next->next; 212 | } 213 | 214 | return $current; 215 | } 216 | 217 | public static function circular(self $list): bool 218 | { 219 | $current = $list->head; 220 | if (!$current) { 221 | return false; 222 | } 223 | 224 | $sentinel = $list->head; 225 | while ($sentinel->next && $sentinel->next->next) { 226 | $current = $current->next; 227 | $sentinel = $sentinel->next->next; 228 | 229 | if ($current === $sentinel) { 230 | return true; 231 | } 232 | } 233 | 234 | return false; 235 | } 236 | 237 | public static function fromLast(self $list, int $n): ?NodeComplete 238 | { 239 | $current = $list->head; 240 | 241 | if (!$current) { 242 | return null; 243 | } 244 | 245 | $sentinel = $list->head; 246 | 247 | while ($n-- > 0) { 248 | $sentinel = $sentinel->next; 249 | } 250 | 251 | while ($sentinel->next) { 252 | $current = $current->next; 253 | $sentinel = $sentinel->next; 254 | } 255 | 256 | return $current; 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Interview Exercises · [![Build Status](https://img.shields.io/travis/com/azdanov/php-interview-exercises/master.svg?logo=travis)](https://travis-ci.com/azdanov/php-interview-exercises) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-blue.svg?logo=github)](http://makeapullrequest.com) [![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors) 2 | 3 | __[NO LONGER MAINTAINED]__: The exercises should still be useful when preparing for a PHP interview, but I'm not able to maintain the repository. 4 | 5 | A number of exercises to practice whiteboard interview questions in PHP. 6 | 7 | Inside `exercises` directory, you can find folders containing the exercises and completed versions. 8 | There are multiple ways to solve each problem, feel free to experiment. 9 | 10 | If you are stuck don't be afraid to search online for a pseudo-code algorithm or a solution. Even by copying and later analyzing the solution you will gain lots of experience in problem solving. 11 | 12 | Also check out the tests and see what results are expected. Playing around with [Xdebug](https://xdebug.org/) is a plus. 13 | 14 | It is useful to actually see how algorithms manipulate data. [Visualgo](https://visualgo.net/en) is a good resource for that. 15 | 16 | ## Install 17 | 18 | 1. Clone this repository. 19 | 2. Run `composer install`. 20 | 21 | ## Usage 22 | 23 | 1. Pick an exercise for example `exercises/ReverseString` and inside `tests/ReverseString/ReverseStringTest.php` delete `static::markTestSkipped();`. 24 | 2. Run `./vendor/bin/phpunit` or `./vendor/bin/phpunit-watcher watch` to start tests. 25 | 3. Write implementation. 26 | 27 | This will allow you to write code and be sure that the solution is correct. 28 | 29 | ## Problem Solving Patterns 30 | 31 | To solve certain problems in the most efficient way many [algorithmic patterns](https://cs.lmu.edu/~ray/notes/algpatterns/) are available. 32 | Such as: 33 | 34 | - Frequency Counter 35 | - Multiple Pointers 36 | - Sliding Window 37 | - Divide and Conquer 38 | - Backtracking 39 | 40 | And more... 41 | 42 | ## Suggested Path 43 | 44 | - [FizzBuzz](./exercises/FizzBuzz/FizzBuzz.php) 45 | - [Numbers](./exercises/Numbers/Numbers.php) 46 | - [Reverse](./exercises/Reverse/Reverse.php) 47 | - [Palindrome](./exercises/Palindrome/Palindrome.php) 48 | - [MaxChar](./exercises/MaxChar/MaxChar.php) 49 | - [ArrayGroup](./exercises/ArrayGroup/ArrayGroup.php) 50 | - [Capitalize](./exercises/Capitalize/Capitalize.php) 51 | - [Vowels](./exercises/Vowels/Vowels.php) 52 | - [Anagram](./exercises/Anagram/Anagram.php) 53 | - [MaxSubArraySum](./exercises/MaxSubArraySum/MaxSubArraySum.php) 54 | - [Search](./exercises/Search/Search.php) 55 | - [Ladder](./exercises/Ladder/Ladder.php) 56 | - [Pyramid](./exercises/Pyramid/Pyramid.php) 57 | - [Spiral](./exercises/Spiral/Spiral.php) 58 | - [Fibonacci](./exercises/Fibonacci/Fibonacci.php) 59 | - [Events](./exercises/Events/Events.php) 60 | - [Queue](./exercises/Queue/Queue.php) 61 | - [Stack](./exercises/Stack/Stack.php) 62 | - [QueueFromStacks](./exercises/QueueFromStacks/QueueFromStacks.php) 63 | - [Sort](./exercises/Sort/Sort.php) 64 | - [LinkedList](./exercises/LinkedList/LinkedList.php) 65 | - [Tree](./exercises/Tree/Tree.php) 66 | - [BinarySearchTree](./exercises/BinarySearchTree/BinarySearchTree.php) 67 | - [MaxHeap](./exercises/MaxHeap/MaxHeap.php) 68 | 69 | ## Aside 70 | 71 | [![Exercise Count](https://img.shields.io/endpoint.svg?url=https://php-interview-questions-counter.netlify.com/.netlify/functions/count)](./exercises) 72 | 73 | This Exercise Counter is made with [Shields IO](https://shields.io/endpoint), [Axios](https://github.com/axios/axios), [GitHub GraphQL](https://developer.github.com/v4/) and [Netlify Functions](https://www.netlify.com/docs/functions/). 74 | 75 | ## Contributing 76 | 77 | Do you have an idea for an exercise or a better solution? Submit a PR! 78 | 79 | ## Contributors 80 | 81 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
Anton Ždanov
Anton Ždanov

💻 📖 ⚠️
ahmed
ahmed

💻 ⚠️
Roman Yepanchenko
Roman Yepanchenko

🐛
Fathom
Fathom

🐛 💻
Alexander Katynia
Alexander Katynia

🐛
kevin-schmitt
kevin-schmitt

💻 ⚠️
95 | 96 | 97 | 98 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 99 | 100 | ## License 101 | 102 | [MIT](./LICENSE) 103 | -------------------------------------------------------------------------------- /exercises/Sort/Complete/SortComplete.php: -------------------------------------------------------------------------------- 1 | $input[$i + 1]) { 36 | self::swap($input[$i], $input[$i + 1]); 37 | $noSwap = true; 38 | } 39 | } 40 | } 41 | 42 | return $input; 43 | } 44 | 45 | /** 46 | * @param mixed[] $input 47 | * 48 | * @return mixed[] 49 | */ 50 | public static function selection(array $input): array 51 | { 52 | for ($i = 0, $min = $i, $length = count($input); $i < $length; $min = ++$i) { 53 | for ($j = $i + 1; $j < $length; ++$j) { 54 | if ($input[$j] < $input[$min]) { 55 | $min = $j; 56 | } 57 | } 58 | 59 | if ($i !== $min) { 60 | self::swap($input[$i], $input[$min]); 61 | } 62 | } 63 | 64 | return $input; 65 | } 66 | 67 | /** 68 | * @param mixed[] $input 69 | * 70 | * @return mixed[] 71 | */ 72 | public static function insertion(array $input): array 73 | { 74 | foreach ($input as $i => $value) { 75 | for ($j = $i; $j > 0 && $input[$j - 1] > $value; --$j) { 76 | $input[$j] = $input[$j - 1]; 77 | } 78 | $input[$j] = $value; 79 | } 80 | 81 | return $input; 82 | } 83 | 84 | /** 85 | * @param mixed[] $input 86 | * 87 | * @return mixed[] 88 | */ 89 | public static function merge(array $input): array 90 | { 91 | $length = count($input); 92 | if ($length <= 1) { 93 | return $input; 94 | } 95 | 96 | $middle = intdiv($length, 2); 97 | $left = array_slice($input, 0, $middle); 98 | $right = array_slice($input, $middle); 99 | 100 | $left = self::merge($left); 101 | $right = self::merge($right); 102 | 103 | return self::merger($left, $right); 104 | } 105 | 106 | /** 107 | * @param mixed[] $input 108 | * 109 | * @return mixed[] 110 | */ 111 | public static function quick1( 112 | array &$input, 113 | ?int $left = 0, 114 | ?int $right = null 115 | ): array { 116 | $right = $right ?? count($input) - 1; 117 | 118 | if ($right - $left < 1) { 119 | return []; 120 | } 121 | 122 | $pivotIndex = self::pivot($input, $left, $right); 123 | 124 | self::quick1($input, $left, $pivotIndex - 1); 125 | self::quick1($input, $pivotIndex + 1, $right); 126 | 127 | return $input; 128 | } 129 | 130 | /** 131 | * @param mixed[] $input 132 | * 133 | * @return mixed[] 134 | */ 135 | public static function quick2( 136 | array &$input, 137 | ?int $left = 0, 138 | ?int $right = null 139 | ): void { 140 | $pivot = $left; 141 | $right = $right ?? count($input) - 1; 142 | 143 | if ($right - $left < 1) { 144 | return; 145 | } 146 | 147 | // Avoid bad performance when sorting an already sorted array 148 | self::swap($input[intdiv($left + $right, 2)], $input[$right]); 149 | 150 | for ($i = $left; $i < $right; ++$i) { 151 | if ($input[$i] < $input[$right]) { 152 | self::swap($input[$i], $input[$pivot]); 153 | ++$pivot; 154 | } 155 | } 156 | 157 | self::swap($input[$pivot], $input[$right]); 158 | 159 | self::quick2($input, $left, $pivot - 1); 160 | self::quick2($input, $pivot + 1, $right); 161 | } 162 | 163 | /** 164 | * Only for unsigned integers from 0 to n. 165 | * 166 | * @param mixed[] $input 167 | * 168 | * @return mixed[] 169 | */ 170 | public static function radix(array $input): array 171 | { 172 | $digitsMax = self::mostDigits($input); 173 | 174 | foreach (range(0, $digitsMax) as $i) { 175 | $bucket = array_fill(0, 10, []); 176 | foreach ($input as $item) { 177 | $digit = self::getDigit($item, $i); 178 | $bucket[$digit][] = $item; 179 | } 180 | $input = array_merge(...$bucket); 181 | } 182 | 183 | return $input; 184 | } 185 | 186 | /** @param int[] $input */ 187 | private static function mostDigits(array $input): int 188 | { 189 | $maxDigits = 0; 190 | foreach ($input as $iValue) { 191 | $maxDigits = max($maxDigits, self::digitCount($iValue)); 192 | } 193 | 194 | return $maxDigits; 195 | } 196 | 197 | private static function digitCount(int $value): int 198 | { 199 | if ($value === 0) { 200 | return 1; 201 | } 202 | 203 | return (int) floor(log10(abs($value))) + 1; 204 | } 205 | 206 | private static function getDigit(int $value, int $position): int 207 | { 208 | return floor(abs($value) / 10 ** $position) % 10; 209 | } 210 | 211 | /** 212 | * @param mixed $x 213 | * @param mixed $y 214 | */ 215 | private static function swap(&$x, &$y): void 216 | { 217 | $tmp = $x; 218 | $x = $y; 219 | $y = $tmp; 220 | } 221 | 222 | /** 223 | * Helper method for Merge sort. 224 | * 225 | * @param mixed[] $left 226 | * @param mixed[] $right 227 | * 228 | * @return mixed[] 229 | */ 230 | private static function merger(array $left, array $right): array 231 | { 232 | $results = []; 233 | 234 | while (count($left) && count($right)) { 235 | if ($left[0] < $right[0]) { 236 | $results[] = array_shift($left); 237 | } else { 238 | $results[] = array_shift($right); 239 | } 240 | } 241 | 242 | return array_merge($results, $left, $right); 243 | } 244 | 245 | /** @param mixed[] $input */ 246 | private static function pivot( 247 | array &$input, 248 | ?int $start = 0, 249 | ?int $end = null 250 | ): int { 251 | $end = $end ?? count($input) - 1; 252 | $pivot = $input[$start]; 253 | $swapIndex = $start; 254 | 255 | for ($i = $start + 1; $i <= $end; ++$i) { 256 | if ($input[$i] < $pivot) { 257 | self::swap($input[++$swapIndex], $input[$i]); 258 | } 259 | } 260 | 261 | self::swap($input[$start], $input[$swapIndex]); 262 | 263 | return $swapIndex; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /tests/LinkedList/LinkedListTest.php: -------------------------------------------------------------------------------- 1 | list = new LinkedList(); 19 | } 20 | 21 | public function testInsertFirst(): void 22 | { 23 | self::markTestSkipped(); 24 | $this->list->insertFirst(0); 25 | $this->list->insertFirst(1); 26 | $this->list->insertFirst(2); 27 | 28 | self::assertSame(2, $this->list->head->data); 29 | self::assertSame(1, $this->list->head->next->data); 30 | self::assertSame(0, $this->list->head->next->next->data); 31 | } 32 | 33 | public function testInsertLast(): void 34 | { 35 | self::markTestSkipped(); 36 | $this->list->insertLast(0); 37 | $this->list->insertLast(1); 38 | $this->list->insertLast(2); 39 | 40 | self::assertSame(0, $this->list->head->data); 41 | self::assertSame(1, $this->list->head->next->data); 42 | self::assertSame(2, $this->list->head->next->next->data); 43 | } 44 | 45 | public function testSize(): void 46 | { 47 | self::markTestSkipped(); 48 | $this->list->head = new Node(0, new Node(1)); 49 | 50 | self::assertSame(2, $this->list->size()); 51 | } 52 | 53 | public function testGetFirst(): void 54 | { 55 | self::markTestSkipped(); 56 | $this->list->head = new Node(0, new Node(1)); 57 | 58 | $first = $this->list->getFirst(); 59 | self::assertSame(0, $first->data); 60 | self::assertInstanceOf(Node::class, $first->next); 61 | self::assertSame(1, $first->next->data); 62 | } 63 | 64 | public function testGetLast(): void 65 | { 66 | self::markTestSkipped(); 67 | $this->list->head = new Node(0, new Node(1)); 68 | 69 | $last = $this->list->getLast(); 70 | self::assertSame(1, $last->data); 71 | self::assertNull($last->next); 72 | } 73 | 74 | public function testClear(): void 75 | { 76 | self::markTestSkipped(); 77 | $this->list->head = new Node(0, new Node(1)); 78 | 79 | $this->list->clear(); 80 | 81 | $last = $this->list->head; 82 | self::assertNull($last); 83 | } 84 | 85 | public function testRemoveFirst(): void 86 | { 87 | self::markTestSkipped(); 88 | $this->list->head = new Node(0, new Node(1, new Node(2))); 89 | 90 | $this->list->removeFirst(); 91 | 92 | $first = $this->list->head; 93 | self::assertSame(1, $first->data); 94 | self::assertInstanceOf(Node::class, $first->next); 95 | self::assertSame(2, $first->next->data); 96 | } 97 | 98 | public function testRemoveFirstEmpty(): void 99 | { 100 | self::markTestSkipped(); 101 | $this->list->removeFirst(); 102 | self::assertTrue(true); 103 | } 104 | 105 | public function testRemoveLast(): void 106 | { 107 | self::markTestSkipped(); 108 | $this->list->head = new Node(0, new Node(1, new Node(2))); 109 | 110 | $this->list->removeLast(); 111 | 112 | $last = $this->list->head->next; 113 | self::assertSame(1, $last->data); 114 | self::assertNull($last->next); 115 | } 116 | 117 | public function testRemoveLastEmpty(): void 118 | { 119 | self::markTestSkipped(); 120 | $this->list->removeLast(); 121 | self::assertTrue(true); 122 | } 123 | 124 | public function testGetAt(): void 125 | { 126 | self::markTestSkipped(); 127 | $this->list->head = new Node(0, new Node(1, new Node(2))); 128 | 129 | $first = $this->list->getAt(0); 130 | self::assertSame(0, $first->data); 131 | 132 | $last = $this->list->getAt(2); 133 | self::assertSame(2, $last->data); 134 | } 135 | 136 | public function testGetAtEmpty(): void 137 | { 138 | self::markTestSkipped(); 139 | $first = $this->list->getAt(0); 140 | self::assertNull($first); 141 | 142 | $second = $this->list->getAt(1); 143 | self::assertNull($second); 144 | } 145 | 146 | public function testRemoveAtEmpty(): void 147 | { 148 | self::markTestSkipped(); 149 | $this->list->removeAt(0); 150 | $this->list->removeAt(1); 151 | $this->list->removeAt(2); 152 | self::assertTrue(true); 153 | } 154 | 155 | public function testRemoveAtOutOfBound(): void 156 | { 157 | self::markTestSkipped(); 158 | $this->list->head = new Node(0); 159 | 160 | $this->list->removeAt(1); 161 | 162 | $first = $this->list->head; 163 | self::assertNull($first); 164 | } 165 | 166 | public function testRemoveAtFirst(): void 167 | { 168 | self::markTestSkipped(); 169 | $this->list->head = new Node(0, new Node(1, new Node(2, new Node(3)))); 170 | 171 | $first = $this->list->head; 172 | self::assertSame(0, $first->data); 173 | 174 | $this->list->removeAt(0); 175 | 176 | $first = $this->list->head; 177 | self::assertSame(1, $first->data); 178 | } 179 | 180 | public function testRemoveAtIndex(): void 181 | { 182 | self::markTestSkipped(); 183 | $this->list->head = new Node(0, new Node(1, new Node(2, new Node(3)))); 184 | 185 | $first = $this->list->head->next; 186 | self::assertSame(1, $first->data); 187 | 188 | $this->list->removeAt(0); 189 | 190 | $first = $this->list->head->next; 191 | self::assertSame(2, $first->data); 192 | } 193 | 194 | public function testRemoveAtLast(): void 195 | { 196 | self::markTestSkipped(); 197 | $this->list->head = new Node(0, new Node(1, new Node(2, new Node(3)))); 198 | 199 | $first = $this->list->head->next->next->next; 200 | self::assertSame(3, $first->data); 201 | 202 | $this->list->removeAt(3); 203 | 204 | $first = $this->list->head->next->next; 205 | self::assertSame(2, $first->data); 206 | } 207 | 208 | public function testInsertAtEmpty(): void 209 | { 210 | self::markTestSkipped(); 211 | $this->list->insertAt('a', 0); 212 | 213 | self::assertSame('a', $this->list->head->data); 214 | } 215 | 216 | public function testInsertAtNegativeOutOfBound(): void 217 | { 218 | self::markTestSkipped(); 219 | $this->list->head = new Node(0, new Node(1, new Node(2, new Node(3)))); 220 | 221 | $this->list->insertAt('a', -10); 222 | 223 | self::assertSame('a', $this->list->head->data); 224 | self::assertSame(0, $this->list->head->next->data); 225 | } 226 | 227 | public function testInsertAt0(): void 228 | { 229 | self::markTestSkipped(); 230 | $this->list->head = new Node(0, new Node(1, new Node(2, new Node(3)))); 231 | 232 | $this->list->insertAt('a', 0); 233 | 234 | self::assertSame('a', $this->list->head->data); 235 | self::assertSame(0, $this->list->head->next->data); 236 | } 237 | 238 | public function testInsertAtMiddle(): void 239 | { 240 | self::markTestSkipped(); 241 | $this->list->head = new Node(0, new Node(1, new Node(2, new Node(3)))); 242 | 243 | $this->list->insertAt('a', 2); 244 | 245 | self::assertSame(0, $this->list->head->data); 246 | self::assertSame(1, $this->list->head->next->data); 247 | self::assertSame('a', $this->list->head->next->next->data); 248 | self::assertSame(2, $this->list->head->next->next->next->data); 249 | self::assertSame(3, $this->list->head->next->next->next->next->data); 250 | } 251 | 252 | public function testInsertAtLast(): void 253 | { 254 | self::markTestSkipped(); 255 | $this->list->head = new Node(0, new Node(1)); 256 | 257 | $this->list->insertAt('hi', 2); 258 | 259 | self::assertSame(0, $this->list->head->data); 260 | self::assertSame(1, $this->list->head->next->data); 261 | self::assertSame('hi', $this->list->head->next->next->data); 262 | } 263 | 264 | public function testInsertAtOutOfBound(): void 265 | { 266 | self::markTestSkipped(); 267 | $this->list->head = new Node(0, new Node(1)); 268 | 269 | $this->list->insertAt('hi', 20); 270 | 271 | self::assertSame(0, $this->list->head->data); 272 | self::assertSame(1, $this->list->head->next->data); 273 | self::assertSame('hi', $this->list->head->next->next->data); 274 | } 275 | 276 | public function testForEach(): void 277 | { 278 | self::markTestSkipped(); 279 | $this->list->head = new Node(0, new Node(1, new Node(2, new Node(3)))); 280 | 281 | $keys = []; 282 | $this->list->forEach(static function (&$data, $index) use (&$keys): void { 283 | $keys[] = $index; 284 | $data *= 10; 285 | }); 286 | 287 | self::assertSame([0, 1, 2, 3], $keys); 288 | self::assertSame(0, $this->list->head->data); 289 | self::assertSame(10, $this->list->head->next->data); 290 | self::assertSame(20, $this->list->head->next->next->data); 291 | self::assertSame(30, $this->list->head->next->next->next->data); 292 | } 293 | 294 | public function testForEachAs(): void 295 | { 296 | self::markTestSkipped(); 297 | $this->list->head = new Node(0, new Node(1, new Node(2, new Node(3)))); 298 | 299 | $keys = []; 300 | foreach ($this->list as $key => $item) { 301 | $keys[] = $key; 302 | $item->data *= 10; 303 | } 304 | 305 | self::assertSame([0, 1, 2, 3], $keys); 306 | self::assertSame(0, $this->list->head->data); 307 | self::assertSame(10, $this->list->head->next->data); 308 | self::assertSame(20, $this->list->head->next->next->data); 309 | self::assertSame(30, $this->list->head->next->next->next->data); 310 | } 311 | 312 | public function testForEachAsEmpty(): void 313 | { 314 | self::markTestSkipped(); 315 | foreach ($this->list as $item) { 316 | $item->data *= 10; 317 | } 318 | self::assertTrue(true); 319 | } 320 | 321 | public function testMidpointEmpty(): void 322 | { 323 | self::markTestSkipped(); 324 | $midpoint = LinkedList::midpoint($this->list); 325 | 326 | self::assertNull($midpoint); 327 | } 328 | 329 | public function testMidpointOne(): void 330 | { 331 | self::markTestSkipped(); 332 | $this->list->head = new Node(0); 333 | 334 | $midpoint = LinkedList::midpoint($this->list); 335 | 336 | self::assertSame(0, $midpoint->data); 337 | } 338 | 339 | public function testMidpointTwo(): void 340 | { 341 | self::markTestSkipped(); 342 | $this->list->head = new Node(0, new Node(1)); 343 | 344 | $midpoint = LinkedList::midpoint($this->list); 345 | 346 | self::assertSame(0, $midpoint->data); 347 | } 348 | 349 | public function testMidpointOdd(): void 350 | { 351 | self::markTestSkipped(); 352 | $this->list->head = new Node(0, new Node(1, new Node(2))); 353 | 354 | $midpoint = LinkedList::midpoint($this->list); 355 | 356 | self::assertSame(1, $midpoint->data); 357 | } 358 | 359 | public function testMidpointEven(): void 360 | { 361 | self::markTestSkipped(); 362 | $this->list->head = new Node(0, new Node(1, new Node(2, new Node(3)))); 363 | 364 | $midpoint = LinkedList::midpoint($this->list); 365 | 366 | self::assertSame(1, $midpoint->data); 367 | } 368 | 369 | public function testCircularEmpty(): void 370 | { 371 | self::markTestSkipped(); 372 | self::assertFalse(LinkedList::circular($this->list)); 373 | } 374 | 375 | public function testCircular(): void 376 | { 377 | self::markTestSkipped(); 378 | $list = new LinkedList(); 379 | $a = new Node('a'); 380 | $b = new Node('b'); 381 | $c = new Node('c'); 382 | 383 | $list->head = $a; 384 | $a->next = $b; 385 | $b->next = $c; 386 | $c->next = $b; 387 | 388 | self::assertTrue(LinkedList::circular($list)); 389 | } 390 | 391 | public function testCircularHead(): void 392 | { 393 | self::markTestSkipped(); 394 | $list = new LinkedList(); 395 | $a = new Node('a'); 396 | $b = new Node('b'); 397 | $c = new Node('c'); 398 | 399 | $list->head = $a; 400 | $a->next = $b; 401 | $b->next = $c; 402 | $c->next = $a; 403 | 404 | self::assertTrue(LinkedList::circular($list)); 405 | } 406 | 407 | public function testNonCircular(): void 408 | { 409 | self::markTestSkipped(); 410 | $list = new LinkedList(); 411 | $a = new Node('a'); 412 | $b = new Node('b'); 413 | $c = new Node('c'); 414 | 415 | $list->head = $a; 416 | $a->next = $b; 417 | $b->next = $c; 418 | $c->next = null; 419 | 420 | self::assertFalse(LinkedList::circular($list)); 421 | } 422 | 423 | public function testFromLastEmpty(): void 424 | { 425 | self::markTestSkipped(); 426 | self::assertNull(LinkedList::fromLast($this->list, 2)); 427 | } 428 | 429 | public function testFromLast(): void 430 | { 431 | self::markTestSkipped(); 432 | $this->list->head = new Node(0, new Node(1, new Node(2, new Node(3)))); 433 | 434 | $fromLast = LinkedList::fromLast($this->list, 1); 435 | 436 | self::assertSame(2, $fromLast->data); 437 | } 438 | } 439 | --------------------------------------------------------------------------------