From a053222f462eea7396b64dcc3352cd157dd6a62c Mon Sep 17 00:00:00 2001 From: Gonzalo Date: Sun, 8 Apr 2018 15:51:28 +0200 Subject: [PATCH] first commit --- .gitattributes | 9 + .gitignore | 8 + .travis.yml | 43 +++ CHANGELOG.md | 27 ++ CONTRIBUTING.md | 72 +++++ LICENSE | 21 ++ README.md | 170 +++++++++++ composer.json | 56 ++++ examples/example.php | 46 +++ phpcs.xml | 35 +++ phpunit.xml | 31 ++ src/Match.php | 114 +++++++ src/Matches.php | 247 +++++++++++++++ src/MatchesSolver.php | 73 +++++ src/Solver.php | 89 ++++++ src/SolverInterface.php | 19 ++ test/suite/MatchTest.php | 148 +++++++++ test/suite/MatchesArrayProvidersTrait.php | 60 ++++ test/suite/MatchesSolverTest.php | 201 ++++++++++++ test/suite/MatchesTest.php | 287 ++++++++++++++++++ .../MatchesUniqueValuesProvidersTrait.php | 162 ++++++++++ test/suite/MatchesValuesProvidersTrait.php | 188 ++++++++++++ test/suite/SolverTest.php | 63 ++++ test/suite/ValuesProvidersTrait.php | 162 ++++++++++ 24 files changed, 2331 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 examples/example.php create mode 100644 phpcs.xml create mode 100644 phpunit.xml create mode 100644 src/Match.php create mode 100644 src/Matches.php create mode 100644 src/MatchesSolver.php create mode 100644 src/Solver.php create mode 100644 src/SolverInterface.php create mode 100644 test/suite/MatchTest.php create mode 100644 test/suite/MatchesArrayProvidersTrait.php create mode 100644 test/suite/MatchesSolverTest.php create mode 100644 test/suite/MatchesTest.php create mode 100644 test/suite/MatchesUniqueValuesProvidersTrait.php create mode 100644 test/suite/MatchesValuesProvidersTrait.php create mode 100644 test/suite/SolverTest.php create mode 100644 test/suite/ValuesProvidersTrait.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..760caf5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +.gitattributes export-ignore +.gitignore export-ignore +.travis.* export-ignore +.archer.* export-ignore +test export-ignore +*.md export-ignore +LICENCE export-ignore +phpcs.xml export-ignore +phpunit.xml export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ccd6297 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# composer vendor directory +vendor/ + +#build +build/ + +# composer +composer.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..840f215 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,43 @@ +# see http://about.travis-ci.org/docs/user/languages/php/ for more hints +# example: https://github.com/travis-ci-examples/php +language: php + +php: + # aliased to a recent 7.x version + - '7.0' + # aliased to a recent 7.1.x version + - '7.1' + # aliased to a recent 7.1.x version + - '7.2' + # aliased to a recent hhvm version + #- hhvm + - nightly + +# This triggers builds to run on the new TravisCI infrastructure. +# See: http://docs.travis-ci.com/user/workers/container-based-infrastructure/ +sudo: false + +## Cache composer +cache: + directories: + - $HOME/.composer/cache + +matrix: + fast_finish: true # Will finish as soon as a job has failed, or when the only jobs left allow failures. + include: + - php: '7.0' + env: + - 'COMPOSER_FLAGS="--prefer-stable --prefer-lowest"' + +# execute any number of scripts before the test run, custom env's are available as variables +before_script: + - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-dist + +# omitting "script:" will default to phpunit +script: + - vendor/bin/phpcs --standard=phpcs.xml + - vendor/bin/phpunit --configuration phpunit.xml --coverage-text + - vendor/bin/phpunit --configuration phpunit.xml --coverage-clover=coverage.xml + +after_success: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..90d6142 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +Change Log +============ + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html) +as it's described in the [Contributing Guide](CONTRIBUTING.md). + +# Proposals + +We do not give estimated times for completion on `Accepted` Proposals. + +- [Accepted][Accepted] +- [Rejected][Rejected] + +--- +## [v1.0.0][Unreleased] - Unreleased + +`INIT` + + + +[Accepted]: https://github.com/Triun/PHP-Longest-Common-Substring/labels/Accepted +[Rejected]: https://github.com/Triun/PHP-Longest-Common-Substring/labels/Rejected + +[Unreleased]: https://github.com/Triun/PHP-Longest-Common-Substring/compare/1.0.0...HEAD diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..93d0f76 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,72 @@ +# Contributing + +Before you contribute code, please make sure it conforms to the PSR-2 coding standard and that the unit tests still pass. The easiest way to contribute is to work on your own fork. + +If you do this, you can run the following commands to check if everything is ready to submit. + +## Dependencies + +In order to load the dependencies, you should run composer: + +```bash +composer install +``` + +## PSR-2 Specs + +This package follows the PSR-2 coding standard. + +- [PSR-2: Coding Style Guide] +- [OPNsense PSR-2 Coding Style Guide] + +To test if your contribution passes the standard, you can use the command: + +```bash +./vendor/bin/phpcs --standard=phpcs.xml +``` + +Which should give you no output, indicating that there are no coding standard errors. + +## Unit testing + +You can write your own tests and add them to the `tests` directory. + +To run the test command: + +```bash +./vendor/bin/phpunit --configuration phpunit.xml --coverage-text +``` + +Which should give you no failures or errors. + +A coverage and logs will be created in the `build` directory. + +In order to give support to older versions, you should test it also with the lowest composer packages: + +```bash +composer update --prefer-stable --prefer-lowest +``` + +## Branching and pull requests + +As a guideline, please follow this process: + + 1. [Fork the repository]. + 2. Create a topic branch for the change: + - New features should branch from **develop**. + - Bug fixes to existing versions should branch from **master**. + - Please ensure the branch is clearly labelled as a feature or fix. + 3. Make the relevant changes. + 4. [Squash] commits if necessary. + 4. Submit a pull request to the **develop** branch. + +Please note this is a general guideline only. For more information on the +branching structure please see the [git-flow cheatsheet]. + + + +[PSR-2: Coding Style Guide]: http://www.php-fig.org/psr/psr-2/ +[OPNsense PSR-2 Coding Style Guide]: https://docs.opnsense.org/development/guidelines/psr2.html +[Fork the repository]: https://help.github.com/articles/fork-a-repo +[git-flow cheatsheet]: http://danielkummer.github.com/git-flow-cheatsheet/ +[Squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..35d3b20 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Triun + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ff5be8 --- /dev/null +++ b/README.md @@ -0,0 +1,170 @@ +PHP - Longest Common Substring +============================== + +PHP implementation of an algorithm to solve the `longest common substring` problem. + +[![Latest Version on Packagist][ico-version]][link-packagist] +[![Pre Release Version on Packagist][ico-pre-release]][link-packagist] +[![Latest Unstable Version][ico-unstable]][link-packagist] +[![Build Status][ico-travis]][link-travis] +[![Coverage status][ico-codecov]][link-codecov] +[![Total Downloads][ico-downloads]][link-downloads] +[![The most recent stable version is 2.0.0][ico-semver]][link-semver] +[![Software License][ico-license]](LICENSE.md) + +# About + +*PHP-Longest-Common-Subsequence* is a PHP implementation of an algorithm to solve the 'longest common substring' problem. + +From [Wikipedia - Longest common substring problem](https://en.wikipedia.org/wiki/Longest_common_substring_problem): + +> In computer science, the longest common substring problem is to find the longest string (or strings) that is a +> substring (or are substrings) of two or more strings. + +# Installation + +Require [triun/longest-common-substring package](https://packagist.org/packages/triun/longest-common-substring) with [composer](http://getcomposer.org/) +using the following command: + +```bash +composer require triun/longest-common-substring +``` + +# Usage + +## Solver + +```php +use Triun\LongestCommonSubstring\Solver; + +$solver = new Solver(); + +$stringA = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; +$stringB = '56789AB56789ABCDE56789ABCDE56789AB56789A123456789A'; + +// calculates the LCSubstring to be '123456789A' +$result = $solver->solve($stringA, $stringB); +``` + +## Matches solver + +```php +use Triun\LongestCommonSubstring\Solver; + +$solver = new Solver(); + +$stringA = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; +$stringB = '56789AB56789ABCDE56789ABCDE56789AB56789A123456789A'; + +$matches = $matchSolver->solve($stringA, $stringB); + +// calculates the LCSubstring to be '123456789A' +$result = "$matches"; +``` + + +But also can give you the rest of the results which has the same length: + +```php +var_dump($matches->values()); +``` + +``` +array:12 [ + 0 => "123456789A" + 1 => "56789ABCDE" + 2 => "56789ABCDE" + 3 => "123456789A" + 4 => "56789ABCDE" + 5 => "56789ABCDE" + 6 => "123456789A" + 7 => "56789ABCDE" + 8 => "56789ABCDE" + 9 => "123456789A" + 10 => "56789ABCDE" + 11 => "56789ABCDE" +] +``` + +You can use `unique` to skip duplicated values: + +```php +var_dump($matches->unique()); +``` + +``` +array:2 [ + 0 => "123456789A" + 1 => "56789ABCDE" +] +``` + +Or even more information about the matches, like the input strings indexes: + +```php +var_dump($matches->values()); +``` + +``` +array:12 [ + 0 => array:3 [ + "value" => "123456789A" + "length" => 10 + "indexes" => array:2 [ + 0 => 1 + 1 => 40 + ] + ] + 1 => array:3 [ + "value" => "56789ABCDE" + "length" => 10 + "indexes" => array:2 [ + 0 => 5 + 1 => 7 + ] + ] + 2 => array:3 [ + "value" => "56789ABCDE" + "length" => 10 + "indexes" => array:2 [ + 0 => 5 + 1 => 17 + ] + ] + ... +] +``` + +# Issues + +Bug reports and feature requests can be submitted on the +[Github Issue Tracker](https://github.com/Triun/PHP-Longest-Common-Substring/issues). + +# Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for information. + +# License + +This repository is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) + + + +[ico-version]: https://img.shields.io/packagist/v/triun/longest-common-substring.svg +[ico-pre-release]: https://img.shields.io/packagist/vpre/triun/longest-common-substring.svg +[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square +[ico-travis]: https://travis-ci.org/Triun/PHP-Longest-Common-Substring.svg?branch=master +[ico-code-quality]: https://img.shields.io/scrutinizer/g/triun/longest-common-substring.svg?style=flat-square +[ico-downloads]: https://img.shields.io/packagist/dt/triun/longest-common-substring.svg?style=flat-square +[ico-unstable]: https://poser.pugx.org/triun/longest-common-substring/v/unstable +[ico-coveralls]: https://coveralls.io/repos/github/Triun/PHP-Longest-Common-Substring/badge.svg?branch=master "Current test coverage for the develop branch" +[ico-codecov]: https://codecov.io/gh/Triun/PHP-Longest-Common-Substring/branch/master/graph/badge.svg +[ico-semver]: http://img.shields.io/:semver-2.0.0-brightgreen.svg "This project uses semantic versioning" + +[link-packagist]: https://packagist.org/packages/triun/longest-common-substring +[link-travis]: https://travis-ci.org/Triun/PHP-Longest-Common-Substring +[link-downloads]: https://packagist.org/packages/triun/longest-common-substring +[link-author]: https://github.com/Triun +[link-coveralls]: https://coveralls.io/github/Triun/PHP-Longest-Common-Substring?branch=master +[link-codecov]: https://codecov.io/gh/Triun/PHP-Longest-Common-Substring +[link-semver]: http://semver.org/ \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..5ee0d19 --- /dev/null +++ b/composer.json @@ -0,0 +1,56 @@ +{ + "name": "triun/longest-common-substring", + "version": "1.0.0", + "description": "PHP implementation of an algorithm to solve the `longest common substring` problem.", + "license": "MIT", + "type": "library", + "homepage": "https://github.com/Triun", + "support": { + "issues": "https://github.com/Triun/PHP-Longest-Common-Substring/issues", + "source": "https://github.com/Triun/PHP-Longest-Common-Substring" + }, + "keywords": [], + "authors": [ + { + "name": "Gonzalo Moreno", + "homepage": "https://github.com/gonzalom" + } + ], + "archive": { + "exclude": [ + "/.git", + "/.gitattributes", + "/.travis.*", + "/.archer.*", + "/test", + "/build", + "/*.md", + "/LICENCE", + "/phpcs.xml", + "/phpunit.xml" + ] + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5", + "squizlabs/php_codesniffer": "^3.2", + "symfony/var-dumper": "^3.4" + }, + "autoload": { + "psr-4": { + "Triun\\LongestCommonSubstring\\": "src/" + } + }, + "autoload-dev": { + "classmap": [ + "test/suite" + ] + }, + "scripts": { + "test": "vendor/bin/phpunit --configuration phpunit.xml", + "test-ci": "vendor/bin/phpunit --configuration phpunit.xml --coverage-clover build/coverage.xml", + "standards": "vendor/bin/phpcs --standard=phpcs.xml" + } +} diff --git a/examples/example.php b/examples/example.php new file mode 100644 index 0000000..ed16677 --- /dev/null +++ b/examples/example.php @@ -0,0 +1,46 @@ +dump((new VarCloner())->cloneVar($value)); + } else { + var_dump($value); + } +} + +$solver = new Solver(); +$matchSolver = new MatchesSolver(); + +$stringA = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; +$stringB = '56789AB56789ABCDE56789ABCDE56789AB56789A123456789A'; + +$matches = $matchSolver->solve($stringA, $stringB); +$result = $solver->solve($stringA, $stringB); + +dump($stringA); +dump($stringB); +dump($matches); +dump($matches->toArray()); +dump($matches->toJson()); +dump($matches->values()); +dump($matches->unique()); +dump((string)$matches[0]); +dump("$matches[0]"); +dump('' . $matches[0]); +dump($matches->firstValue()); +dump((string)$matches->first()); +dump((string)$matches); +dump($result); + + + diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..4de949f --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,35 @@ + + + + Package's PHP Standard (PSR2) + + + + ./ + + /build + /vendor + + + + + + + + + + + /test/* + + + + + /test/* + + + + + /test/* + + + \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..ff154a2 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,31 @@ + + + + + + ./test/suite + + + + + src/ + + + + + + + + + + \ No newline at end of file diff --git a/src/Match.php b/src/Match.php new file mode 100644 index 0000000..d24871b --- /dev/null +++ b/src/Match.php @@ -0,0 +1,114 @@ +length = $length; + $this->indexes = $indexes; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->value; + } + + /** + * @param string $value + * + * @return Match + */ + public function setValue(string $value): Match + { + $this->value = $value; + + return $this; + } + + /** + * @return array + */ + public function toArray(): array + { + return (array)$this; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize(): array + { + return $this->toArray(); + } + + /** + * @return string + */ + public function serialize(): string + { + return serialize([ + $this->value, + $this->length, + $this->indexes, + ]); + } + + /** + * @param string $data + */ + public function unserialize($data) + { + list( + $this->value, + $this->length, + $this->indexes + ) = unserialize($data); + } + + /** + * @param int $key + * @param int|null $default + * + * @return int|null + */ + public function index(int $key = 0, int $default = null) + { + return array_key_exists($key, $this->indexes) ? $this->indexes[$key] : $default; + } +} diff --git a/src/Matches.php b/src/Matches.php new file mode 100644 index 0000000..4e5cd90 --- /dev/null +++ b/src/Matches.php @@ -0,0 +1,247 @@ +items = array_values($items); + } + + /** + * Has any match. + * + * @return bool + */ + public function has() + { + return count($this->items) > 0; + } + + /** + * @return bool + */ + public function count() + { + return count($this->items); + } + + /** + * Get first match object. + * + * @return \Triun\LongestCommonSubstring\Match + */ + public function first() + { + return array_key_exists(0, $this->items) ? $this->items[0] : null; + } + + /** + * Get all matches objects. + * + * @return \Triun\LongestCommonSubstring\Match[] + */ + public function all() + { + return $this->items; + } + + /** + * Get first value. + * + * @return string + */ + public function firstValue() + { + if (!$this->has()) { + return null; + } + + return $this->items[0]->value; + } + + /** + * Get all matches values. + * + * @return string[] + */ + public function values() + { + return array_map(function (Match $item) { + return $item->value; + }, $this->items); + } + + /** + * @return string[] + */ + public function unique() + { + return array_values(array_unique($this->values())); + } + + /** + * @return string + */ + public function __toString() + { + return $this->firstValue()?: ''; + } + + /** + * Convert instance to an array. + * + * @return array + */ + public function toArray() + { + return array_map(function (Match $item) { + return $item->toArray(); + }, $this->items); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert instance to JSON. + * + * @param int $options + * + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->items); + } + + /** + * Get the value for a given offset. + * + * @param string $offset + * + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + /** + * Set the value at the given offset. + * + * @param string $offset + * @param mixed $value + * + * @return void + */ + public function offsetSet($offset, $value) + { + throw new RuntimeException('Read only'); + } + + /** + * Unset the value at the given offset. + * + * @param string $offset + * + * @return void + */ + public function offsetUnset($offset) + { + throw new RuntimeException('Read only'); + } + + /** + * Return the current element + * + * @return mixed|\Triun\LongestCommonSubstring\Match + */ + public function current() + { + return $this->items[$this->position]; + } + + /** + * Move forward to next element + */ + public function next() + { + $this->position++; + } + + /** + * Return the key of the current element + * + * @return mixed|int + */ + public function key() + { + return $this->position; + } + + /** + * Checks if current position is valid + * + * @return boolean The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + */ + public function valid() + { + return $this->position < $this->count(); + } + + /** + * Rewind the Iterator to the first element + */ + public function rewind() + { + $this->position = 0; + } +} diff --git a/src/MatchesSolver.php b/src/MatchesSolver.php new file mode 100644 index 0000000..5079be1 --- /dev/null +++ b/src/MatchesSolver.php @@ -0,0 +1,73 @@ +value = substr($stringA, $result->index(), $result->length); + + return $result; + }, $longestIndexes); + } + + /** + * @param string $stringA + * @param string $stringB + * + * @return Matches + */ + public function solve(string $stringA, string $stringB) + { + if (func_num_args() > 2) { + // TODO: Get the best combination, not just the first one. + $arguments = func_get_args(); + array_splice($arguments, 0, 2, [$this->solve($stringA, $stringB)]); + + return call_user_func_array([$this, 'solve'], $arguments); + } + + return new Matches(parent::solve($stringA, $stringB)); + } +} diff --git a/src/Solver.php b/src/Solver.php new file mode 100644 index 0000000..74ee735 --- /dev/null +++ b/src/Solver.php @@ -0,0 +1,89 @@ + 2) { + $arguments = func_get_args(); + array_splice($arguments, 0, 2, [$this->solve($stringA, $stringB)]); + + return call_user_func_array([$this, 'solve'], $arguments); + } + + $charsA = str_split($stringA); + $charsB = str_split($stringB); + + $matrix = array_fill_keys(array_keys($charsA), array_fill_keys(array_keys($charsB), 0)); + $longestLength = 0; + $longestIndexes = []; + + foreach ($charsA as $i => $charA) { + foreach ($charsB as $j => $charB) { + if ($charA === $charB) { + if (0 === $i || 0 === $j) { + $matrix[$i][$j] = 1; + } else { + $matrix[$i][$j] = $matrix[$i - 1][$j - 1] + 1; + } + + $newIndex = $this->newIndex($matrix, $i, $j); + if ($matrix[$i][$j] > $longestLength) { + $longestLength = $matrix[$i][$j]; + $longestIndexes = [$newIndex]; + } elseif ($matrix[$i][$j] === $longestLength) { + $longestIndexes[] = $newIndex; + } + } else { + $matrix[$i][$j] = 0; + } + } + } + + return $this->result($longestIndexes, $longestLength, $stringA, $stringB, $matrix); + } +} diff --git a/src/SolverInterface.php b/src/SolverInterface.php new file mode 100644 index 0000000..e3f69c9 --- /dev/null +++ b/src/SolverInterface.php @@ -0,0 +1,19 @@ +assertInstanceOf(\JsonSerializable::class, $match); + $this->assertInstanceOf(\Serializable::class, $match); + } + + /** + * @dataProvider valuesProvider + * + * @param string $value + */ + public function testValues(string $value) + { + $match = $this->makeFakeMatch($value); + + $this->assertSame($value, $match->value); + $this->assertSame($value, $match->__toString()); + $this->assertSame($value, (string)$match); + $this->assertEquals($value, $match); + } + + /** + * @dataProvider indexesProvider + * + * @param int[] $indexes + */ + public function testIndexes(array $indexes) + { + $match = $this->makeFakeMatch('', $indexes); + $noOffset = count($indexes); + + $this->assertSame($indexes, $match->indexes); + $this->assertSame($indexes[0], $match->index()); + $this->assertSame($indexes[0], $match->index(0)); + $this->assertSame($indexes[1], $match->index(1)); + $this->assertSame(null, $match->index($noOffset)); + $this->assertSame(101, $match->index($noOffset, 101)); + } + + public function testNoIndexes() + { + $match = $this->makeFakeMatch('', []); + + $this->assertSame(null, $match->index()); + $this->assertSame(null, $match->index(0)); + } + + /** + * @dataProvider matchProvider + * + * @param string $value + * @param int[] $indexes + */ + public function testArray(string $value, array $indexes) + { + $match = $this->makeFakeMatch($value, $indexes); + + $expected = [ + 'value' => $value, + 'length' => strlen($value), + 'indexes' => $indexes, + ]; + + $this->assertSame($expected, $match->toArray()); + $this->assertSame($expected, $match->jsonSerialize()); + } + + /** + * @dataProvider serializeProvider + * + * @param string $value + * @param int[] $indexes + * @param string $serialized + */ + public function testSerialize(string $value, array $indexes, string $serialized) + { + $match = $this->makeFakeMatch($value, $indexes); + + $this->assertSame($serialized, serialize($match)); + + $aux = unserialize($serialized); + + $this->assertNotSame($match, $aux); + $this->assertEquals($match, $aux); + } + + public function valuesProvider() + { + return [ + [ + '', + 'Donec ullamcorper nulla non metus auctor fringilla.', + ], + ]; + } + + public function indexesProvider() + { + return [ + [ + [10, 20, 30, 40], + ], + ]; + } + + public function matchProvider() + { + return [ + [ + 'Donec ullamcorper nulla non metus auctor fringilla.', + [10, 20, 30, 40], + ], + ]; + } + + public function serializeProvider() + { + return [ + [ + 'Donec ullamcorper nulla non metus auctor fringilla.', + [10, 20, 30, 40], + //'O:34:"Triun\LongestCommonSubstring\Match":3:{s:5:"value";s:51:"Donec ullamcorper nulla non metus auctor fringilla.";s:6:"length";i:51;s:7:"indexes";a:4:{i:0;i:10;i:1;i:20;i:2;i:30;i:3;i:40;}}', + 'C:34:"Triun\LongestCommonSubstring\Match":124:{a:3:{i:0;s:51:"Donec ullamcorper nulla non metus auctor fringilla.";i:1;i:51;i:2;a:4:{i:0;i:10;i:1;i:20;i:2;i:30;i:3;i:40;}}}', + ], + ]; + } + + /** + * @param string|null $value + * @param array $indexes + * + * @return \Triun\LongestCommonSubstring\Match + */ + protected function makeFakeMatch(string $value = null, array $indexes = []) + { + return (new Match($indexes, strlen($value)))->setValue($value); + } +} diff --git a/test/suite/MatchesArrayProvidersTrait.php b/test/suite/MatchesArrayProvidersTrait.php new file mode 100644 index 0000000..0e715c3 --- /dev/null +++ b/test/suite/MatchesArrayProvidersTrait.php @@ -0,0 +1,60 @@ + [ + 'ABC', + 'DEF', + [], + ], + 'Empty values' => [ + '', + '', + [ + [ + 'value' => '', + 'length' => 1, // "\n" + 'indexes' => [0, 0], + ], + ], + ], + ]; + } + + public function twoStringsOrderedMatchesArrayProvider() + { + return [ + 'Hexadecimal' => [ + '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', + '23415A32443FC243D123456789AC342553', + [ + [ + 'value' => '123456789A', + 'length' => 10, + 'indexes' => [1, 17], + ], + [ + 'value' => '123456789A', + 'length' => 10, + 'indexes' => [17, 17], + ], + [ + 'value' => '123456789A', + 'length' => 10, + 'indexes' => [33, 17], + ], + [ + 'value' => '123456789A', + 'length' => 10, + 'indexes' => [49, 17], + ], + ], + ], + ]; + } +} diff --git a/test/suite/MatchesSolverTest.php b/test/suite/MatchesSolverTest.php new file mode 100644 index 0000000..9691495 --- /dev/null +++ b/test/suite/MatchesSolverTest.php @@ -0,0 +1,201 @@ +assertEquals($expected, static::$solver->solve($stringLeft, $stringRight)); + $this->assertEquals($expected, static::$solver->solve($stringRight, $stringLeft)); + } + + /** + * @dataProvider twoStringsOrderedValuesProvider + * + * @param string $stringLeft + * @param string $stringRight + * @param string $expected + */ + public function testTwoStringsOrderedValues($stringLeft, $stringRight, $expected) + { + $this->assertEquals($expected, static::$solver->solve($stringLeft, $stringRight)); + } + + /** + * @dataProvider threeStringsSymmetricValuesProvider + * + * @param string $stringA + * @param string $stringB + * @param string $stringC + * @param string $expected + */ + public function testThreeStringsSymmetricValues($stringA, $stringB, $stringC, $expected) + { + $this->assertEquals($expected, static::$solver->solve($stringA, $stringB, $stringC)); + $this->assertEquals($expected, static::$solver->solve($stringA, $stringC, $stringB)); + $this->assertEquals($expected, static::$solver->solve($stringB, $stringA, $stringC)); + $this->assertEquals($expected, static::$solver->solve($stringB, $stringC, $stringA)); + $this->assertEquals($expected, static::$solver->solve($stringC, $stringA, $stringB)); + $this->assertEquals($expected, static::$solver->solve($stringC, $stringB, $stringA)); + } + + /* + |-------------------------------------------------------------------------- + | Unique match values + |-------------------------------------------------------------------------- + */ + + /** + * @dataProvider twoStringsSymmetricMatchesUniqueValuesProvider + * + * @param string $stringLeft + * @param string $stringRight + * @param string $expected + */ + public function testTwoStringsSymmetricMatchesUniqueValues($stringLeft, $stringRight, $expected) + { + $this->assertSame($expected, static::$solver->solve($stringLeft, $stringRight)->unique()); + $this->assertSame($expected, static::$solver->solve($stringRight, $stringLeft)->unique()); + } + + /** + * @dataProvider twoStringsOrderedMatchesUniqueValuesProvider + * + * @param string $stringLeft + * @param string $stringRight + * @param string $expected + */ + public function testTwoStringsOrderedMatchesUniqueValues($stringLeft, $stringRight, $expected) + { + $this->assertSame($expected, static::$solver->solve($stringLeft, $stringRight)->unique()); + } + + /** + * @dataProvider threeStringsSymmetricMatchesUniqueValuesProvider + * + * @param string $stringA + * @param string $stringB + * @param string $stringC + * @param string $expected + */ + public function testThreeStringsSymmetricMatchesUniqueValues($stringA, $stringB, $stringC, $expected) + { + $this->assertSame($expected, static::$solver->solve($stringA, $stringB, $stringC)->unique()); + $this->assertSame($expected, static::$solver->solve($stringA, $stringC, $stringB)->unique()); + $this->assertSame($expected, static::$solver->solve($stringB, $stringA, $stringC)->unique()); + $this->assertSame($expected, static::$solver->solve($stringB, $stringC, $stringA)->unique()); + $this->assertSame($expected, static::$solver->solve($stringC, $stringA, $stringB)->unique()); + $this->assertSame($expected, static::$solver->solve($stringC, $stringB, $stringA)->unique()); + } + + /* + |-------------------------------------------------------------------------- + | All match values + |-------------------------------------------------------------------------- + */ + + /** + * @dataProvider twoStringsSymmetricMatchesValuesProvider + * + * @param string $stringLeft + * @param string $stringRight + * @param string $expected + */ + public function testTwoStringsSymmetricMatchesValues($stringLeft, $stringRight, $expected) + { + $this->assertSame($expected, static::$solver->solve($stringLeft, $stringRight)->values()); + $this->assertSame($expected, static::$solver->solve($stringRight, $stringLeft)->values()); + } + + /** + * @dataProvider twoStringsOrderedMatchesValuesProvider + * + * @param string $stringLeft + * @param string $stringRight + * @param string $expected + */ + public function testTwoStringsOrderedMatchesValues($stringLeft, $stringRight, $expected) + { + $this->assertSame($expected, static::$solver->solve($stringLeft, $stringRight)->values()); + } + + /** + * @dataProvider threeStringsSymmetricMatchesValuesProvider + * + * @param string $stringA + * @param string $stringB + * @param string $stringC + * @param string $expected + */ + public function testThreeStringsSymmetricMatchesValues($stringA, $stringB, $stringC, $expected) + { + $this->assertSame($expected, static::$solver->solve($stringA, $stringB, $stringC)->values()); + $this->assertSame($expected, static::$solver->solve($stringA, $stringC, $stringB)->values()); + $this->assertSame($expected, static::$solver->solve($stringB, $stringA, $stringC)->values()); + $this->assertSame($expected, static::$solver->solve($stringB, $stringC, $stringA)->values()); + $this->assertSame($expected, static::$solver->solve($stringC, $stringA, $stringB)->values()); + $this->assertSame($expected, static::$solver->solve($stringC, $stringB, $stringA)->values()); + } + + /* + |-------------------------------------------------------------------------- + | All match to array + |-------------------------------------------------------------------------- + */ + + /** + * @dataProvider twoStringsSymmetricalMatchesArrayProvider + * + * @param string $stringLeft + * @param string $stringRight + * @param string $expected + */ + public function testTwoStringsSymmetricalMatchesArray($stringLeft, $stringRight, $expected) + { + $this->assertSame($expected, static::$solver->solve($stringLeft, $stringRight)->toArray()); + $this->assertSame($expected, static::$solver->solve($stringRight, $stringLeft)->toArray()); + } + + /** + * @dataProvider twoStringsOrderedMatchesArrayProvider + * + * @param string $stringLeft + * @param string $stringRight + * @param string $expected + */ + public function testTwoStringsOrderedMatchesArray($stringLeft, $stringRight, $expected) + { + $this->assertSame($expected, static::$solver->solve($stringLeft, $stringRight)->toArray()); + } +} diff --git a/test/suite/MatchesTest.php b/test/suite/MatchesTest.php new file mode 100644 index 0000000..d044a12 --- /dev/null +++ b/test/suite/MatchesTest.php @@ -0,0 +1,287 @@ +assertInstanceOf(\ArrayAccess::class, $matches); + $this->assertInstanceOf(\Iterator::class, $matches); + $this->assertInstanceOf(\JsonSerializable::class, $matches); + $this->assertInstanceOf(\Countable::class, $matches); + } + + /** + * @dataProvider valuesProvider + * + * @param array $items + */ + public function testCount(array $items) + { + $matches = $this->makeFakeCollection($items); + $total = count($items); + + $this->assertSame($total, $matches->count()); + $this->assertTrue($matches->has()); + } + + /** + * @dataProvider valuesProvider + * + * @param array $items + */ + public function testAccessors(array $items) + { + $matches = $this->makeFakeCollection($items); + + $all = array_map([$this, 'makeFakeMatch'], $items); + + $this->assertEquals($all, $matches->all()); + + $first = $this->makeFakeMatch($items[0]); + + $this->assertEquals($first, $matches->first()); + } + + /** + * @dataProvider valuesProvider + * + * @param array $items + * @param array $values + * @param array $unique + */ + public function testValues(array $items, array $values, array $unique) + { + $matches = $this->makeFakeCollection($items); + + $this->assertSame($values, $matches->values()); + $this->assertSame($unique, $matches->unique()); + $this->assertSame($values[0], $matches->firstValue()); + $this->assertSame($values[0], (string)$matches); + } + + /** + * @dataProvider valuesProvider + * + * @param array $items + */ + public function testArray(array $items) + { + $matches = $this->makeFakeCollection($items); + + $array = array_map(function ($item) { + return [ + 'value' => $item[0], + 'length' => strlen($item[0]), + 'indexes' => $item[1], + ]; + }, $items); + + $this->assertSame($array, $matches->toArray()); + $this->assertSame($array, $matches->jsonSerialize()); + $this->assertSame(json_encode($array), $matches->toJson()); + } + + /** + * @dataProvider valuesProvider + * + * @param array $items + */ + public function testArrayAccess(array $items) + { + $matches = $this->makeFakeCollection($items); + $noOffset = count($items); + $last = count($items) - 1; + + $this->assertTrue($matches->offsetExists(0)); + $this->assertFalse($matches->offsetExists($noOffset)); + $this->assertTrue(isset($matches[0])); + $this->assertFalse(isset($matches[$noOffset])); + + $all = $matches->all(); + + $this->assertEquals($all[0], $matches->offsetGet(0)); + $this->assertEquals($all[0], $matches[0]); + $this->assertEquals($all[$last], $matches->offsetGet($last)); + $this->assertEquals($all[$last], $matches[$last]); + } + + public function testArrayAccessOffsetSetNotAllowed() + { + $matches = new Matches([]); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Read only'); + + $matches->offsetSet(0, 'a'); + } + + public function testArrayAccessOffsetUnsetNotAllowed() + { + $matches = new Matches([]); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Read only'); + + $matches->offsetUnset(0); + } + + /** + * @dataProvider valuesProvider + * + * @param array $items + */ + public function testIterator(array $items) + { + $matches = $this->makeFakeCollection($items); + + $all = $matches->all(); + + $this->assertTrue($matches->valid()); + $this->assertSame(0, $matches->key()); + $this->assertEquals($all[0], $matches->current()); + + $matches->next(); + + if (count($items) > 1) { + $this->assertTrue($matches->valid()); + $this->assertSame(1, $matches->key()); + $this->assertEquals($all[1], $matches->current()); + } else { + $this->assertFalse($matches->valid()); + $this->assertSame(1, $matches->key()); + } + + $matches->rewind(); + + $this->assertSame(0, $matches->key()); + } + + public function testEmpty() + { + $matches = $this->makeFakeCollection([]); + + $this->assertSame(0, $matches->count()); + $this->assertFalse($matches->has()); + + $this->assertSame(null, $matches->first()); + $this->assertSame(null, $matches->firstValue()); + $this->assertSame('', (string)$matches); + + $this->assertSame([], $matches->all()); + $this->assertSame([], $matches->values()); + $this->assertSame([], $matches->unique()); + + $this->assertSame([], $matches->toArray()); + $this->assertSame([], $matches->jsonSerialize()); + $this->assertSame('[]', $matches->toJson()); + } + + public function valuesProvider() + { + return [ + 'One match' => [ + 'matches' => [ + [ + 'Donec ullamcorper nulla non metus auctor fringilla.', + [10, 20, 30, 40], + ], + ], + 'values' => [ + 'Donec ullamcorper nulla non metus auctor fringilla.', + ], + 'unique' => [ + 'Donec ullamcorper nulla non metus auctor fringilla.', + ], + ], + 'Tree matches' => [ + 'matches' => [ + [ + 'Fermentum', + [2, 58], + ], + [ + 'Donec sed odio dui. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Maecenas sed diam eget risus varius blandit sit amet non magna. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.', + [130, 90], + ], + [ + 'Curabitur blandit tempus porttitor', + [323, 56], + ], + ], + 'values' => [ + 'Fermentum', + 'Donec sed odio dui. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Maecenas sed diam eget risus varius blandit sit amet non magna. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.', + 'Curabitur blandit tempus porttitor', + ], + 'unique' => [ + 'Fermentum', + 'Donec sed odio dui. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Maecenas sed diam eget risus varius blandit sit amet non magna. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.', + 'Curabitur blandit tempus porttitor', + ], + ], + 'Repeated values' => [ + 'matches' => [ + [ + 'Fermentum', + [2, 58], + ], + [ + 'Donec sed odio dui. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Maecenas sed diam eget risus varius blandit sit amet non magna. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.', + [130, 90], + ], + [ + 'Curabitur blandit tempus porttitor', + [323, 56], + ], + [ + 'Curabitur blandit tempus porttitor', + [405, 56], + ], + [ + 'Fermentum', + [20, 58], + ], + ], + 'values' => [ + 'Fermentum', + 'Donec sed odio dui. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Maecenas sed diam eget risus varius blandit sit amet non magna. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.', + 'Curabitur blandit tempus porttitor', + 'Curabitur blandit tempus porttitor', + 'Fermentum', + ], + 'unique' => [ + 'Fermentum', + 'Donec sed odio dui. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Maecenas sed diam eget risus varius blandit sit amet non magna. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.', + 'Curabitur blandit tempus porttitor', + ], + ], + ]; + } + + /** + * @param array $items + * + * @return \Triun\LongestCommonSubstring\Matches + */ + protected function makeFakeCollection(array $items) + { + return new Matches(array_map([$this, 'makeFakeMatch'], $items)); + } + + /** + * @param array $item + * + * @return \Triun\LongestCommonSubstring\Match + */ + protected function makeFakeMatch(array $item) + { + return (new Match($item[1], strlen($item[0])))->setValue($item[0]); + } +} diff --git a/test/suite/MatchesUniqueValuesProvidersTrait.php b/test/suite/MatchesUniqueValuesProvidersTrait.php new file mode 100644 index 0000000..d33a657 --- /dev/null +++ b/test/suite/MatchesUniqueValuesProvidersTrait.php @@ -0,0 +1,162 @@ + [ + '', + '', + [''], + ], + 'All elements equal' => [ + 'ABC', + 'ABC', + ['ABC'], + ], + 'Second string is a substring of first' => [ + 'ABCDEF', + 'BCDE', + ['BCDE'], + ], + 'Basic common substring' => [ + 'ABDE', + 'ACDF', + ['A', 'D'], + ], + 'Common substring of larger data set' => [ + 'ABCDEF', + 'JADFAFKDFBCDEHJDFG', + ['BCDE'], + ], + 'Single element substring at start' => [ + 'ABCDEF', + 'A', + ['A'], + ], + 'Single element substring at middle' => [ + 'ABCDEF', + 'D', + ['D'], + ], + 'Single element substring at end' => [ + 'ABCDEF', + 'F', + ['F'], + ], + 'Elements after end of first string' => [ + 'JAFAFKDBCEHJDF', + 'JDFAKDFCDEJDFG', + ['JDF'], + ], + 'No common elements' => [ + 'ABC', + 'DEF', + [], + ], + 'No case common elements' => [ + 'ABC', + 'abc', + [], + ], + 'Equal Word' => [ + 'Tortor', + 'Tortor', + ['Tortor'], + ], + 'Similar Word' => [ + 'Color', + 'Colour', + ['Colo'], + ], + 'Word case' => [ + 'color', + 'Colour', + ['olo'], + ], + 'Equal Sentence' => [ + 'Donec ullamcorper nulla non metus auctor fringilla.', + 'Donec ullamcorper nulla non metus auctor fringilla.', + ['Donec ullamcorper nulla non metus auctor fringilla.'], + ], + 'Similar Sentence' => [ + 'Donec ullamcorper nulla non metus auctor fringilla.', + 'Donec ullamcorper nulla no metus auctor fringilla.', + ['Donec ullamcorper nulla no'], + ], + 'Hexadecimal' => [ + '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', + '23415A32443FC243D123456789AC342553', + ['123456789A'], + ], + 'README example' => [ + 'BANANA', + 'ATANA', + ['ANA'], + ], + ]; + } + + public function twoStringsOrderedMatchesUniqueValuesProvider() + { + return [ + 'Reverse string ASC -> DESC' => [ + 'ABCDE', + 'EDCBA', + ['A', 'B', 'C', 'D', 'E'], + ], + 'Reverse string DESC -> ASC' => [ + 'EDCBA', + 'ABCDE', + ['E', 'D', 'C', 'B', 'A'], + ], + 'Order change Natural -> Changed' => [ + 'ABCDEF', + 'ABDCEF', + ['AB', 'EF'], + ], + 'Order change Changed -> Natural' => [ + 'ABDCEF', + 'ABCDEF', + ['AB', 'EF'], + ], + 'Hexadecimal' => [ + '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', + '56789AB56789ABCDE56789ABCDE56789AB56789A123456789A', + ['123456789A', '56789ABCDE'], + ], + 'Hexadecimal Reverse' => [ + '56789AB56789ABCDE56789ABCDE56789AB56789A123456789A', + '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', + ['56789ABCDE', '123456789A'], + ], + ]; + } + + public function threeStringsSymmetricMatchesUniqueValuesProvider() + { + return [ + 'No match' => [ + 'ABDEGH', + 'JKLMN', + 'OPQRST', + [], + ], + 'One match' => [ + 'ABDEGH3', + 'JKL3MN', + '3OPQRST', + ['3'], + ], + 'Wikipedia Example' => [ + 'ABABC', + 'BABCA', + 'ABCBA', + ['ABC'], + ], + ]; + } +} diff --git a/test/suite/MatchesValuesProvidersTrait.php b/test/suite/MatchesValuesProvidersTrait.php new file mode 100644 index 0000000..d0aa8aa --- /dev/null +++ b/test/suite/MatchesValuesProvidersTrait.php @@ -0,0 +1,188 @@ + [ + '', + '', + [''], + ], + 'All elements equal' => [ + 'ABC', + 'ABC', + ['ABC'], + ], + 'Second string is a substring of first' => [ + 'ABCDEF', + 'BCDE', + ['BCDE'], + ], + 'Basic common substring' => [ + 'ABDE', + 'ACDF', + ['A', 'D'], + ], + 'Common substring of larger data set' => [ + 'ABCDEF', + 'JADFAFKDFBCDEHJDFG', + ['BCDE'], + ], + 'Single element substring at start' => [ + 'ABCDEF', + 'A', + ['A'], + ], + 'Single element substring at middle' => [ + 'ABCDEF', + 'D', + ['D'], + ], + 'Single element substring at end' => [ + 'ABCDEF', + 'F', + ['F'], + ], + 'Elements after end of first string' => [ + 'JAFAFKDBCEHJDF', + 'JDFAKDFCDEJDFG', + ['JDF', 'JDF'], + ], + 'No common elements' => [ + 'ABC', + 'DEF', + [], + ], + 'No case common elements' => [ + 'ABC', + 'abc', + [], + ], + 'Equal Word' => [ + 'Tortor', + 'Tortor', + ['Tortor'], + ], + 'Similar Word' => [ + 'Color', + 'Colour', + ['Colo'], + ], + 'Word case' => [ + 'color', + 'Colour', + ['olo'], + ], + 'Equal Sentence' => [ + 'Donec ullamcorper nulla non metus auctor fringilla.', + 'Donec ullamcorper nulla non metus auctor fringilla.', + ['Donec ullamcorper nulla non metus auctor fringilla.'], + ], + 'Similar Sentence' => [ + 'Donec ullamcorper nulla non metus auctor fringilla.', + 'Donec ullamcorper nulla no metus auctor fringilla.', + ['Donec ullamcorper nulla no'], + ], + 'Hexadecimal' => [ + '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', + '23415A32443FC243D123456789AC342553', + ['123456789A', '123456789A', '123456789A', '123456789A'], + ], + 'README example' => [ + 'BANANA', + 'ATANA', + ['ANA', 'ANA'], + ], + ]; + } + + public function twoStringsOrderedMatchesValuesProvider() + { + return [ + 'Reverse string ASC -> DESC' => [ + 'ABCDE', + 'EDCBA', + ['A', 'B', 'C', 'D', 'E'], + ], + 'Reverse string DESC -> ASC' => [ + 'EDCBA', + 'ABCDE', + ['E', 'D', 'C', 'B', 'A'], + ], + 'Order change Natural -> Changed' => [ + 'ABCDEF', + 'ABDCEF', + ['AB', 'EF'], + ], + 'Order change Changed -> Natural' => [ + 'ABDCEF', + 'ABCDEF', + ['AB', 'EF'], + ], + 'Hexadecimal' => [ + '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', + '56789AB56789ABCDE56789ABCDE56789AB56789A123456789A', + [ + '123456789A', + '56789ABCDE', + '56789ABCDE', + '123456789A', + '56789ABCDE', + '56789ABCDE', + '123456789A', + '56789ABCDE', + '56789ABCDE', + '123456789A', + '56789ABCDE', + '56789ABCDE', + ], + ], + 'Hexadecimal Reverse' => [ + '56789AB56789ABCDE56789ABCDE56789AB56789A123456789A', + '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', + [ + '56789ABCDE', + '56789ABCDE', + '56789ABCDE', + '56789ABCDE', + '56789ABCDE', + '56789ABCDE', + '56789ABCDE', + '56789ABCDE', + '123456789A', + '123456789A', + '123456789A', + '123456789A', + ], + ], + ]; + } + + public function threeStringsSymmetricMatchesValuesProvider() + { + return [ + 'No match' => [ + 'ABDEGH', + 'JKLMN', + 'OPQRST', + [], + ], + 'One match' => [ + 'ABDEGH3', + 'JKL3MN', + '3OPQRST', + ['3'], + ], + 'Wikipedia Example' => [ + 'ABABC', + 'BABCA', + 'ABCBA', + ['ABC'], + ], + ]; + } +} diff --git a/test/suite/SolverTest.php b/test/suite/SolverTest.php new file mode 100644 index 0000000..364c269 --- /dev/null +++ b/test/suite/SolverTest.php @@ -0,0 +1,63 @@ +assertSame($expected, static::$solver->solve($stringLeft, $stringRight)); + $this->assertSame($expected, static::$solver->solve($stringRight, $stringLeft)); + } + + /** + * @dataProvider twoStringsOrderedValuesProvider + * + * @param string $stringLeft + * @param string $stringRight + * @param string $expected + */ + public function testTwoStringsOrdered($stringLeft, $stringRight, $expected) + { + $this->assertSame($expected, static::$solver->solve($stringLeft, $stringRight)); + } + + /** + * @dataProvider threeStringsSymmetricValuesProvider + * + * @param string $stringA + * @param string $stringB + * @param string $stringC + * @param string $expected + */ + public function testThreeStringsSymmetric($stringA, $stringB, $stringC, $expected) + { + $this->assertSame($expected, static::$solver->solve($stringA, $stringB, $stringC)); + $this->assertSame($expected, static::$solver->solve($stringA, $stringC, $stringB)); + $this->assertSame($expected, static::$solver->solve($stringB, $stringA, $stringC)); + $this->assertSame($expected, static::$solver->solve($stringB, $stringC, $stringA)); + $this->assertSame($expected, static::$solver->solve($stringC, $stringA, $stringB)); + $this->assertSame($expected, static::$solver->solve($stringC, $stringB, $stringA)); + } +} diff --git a/test/suite/ValuesProvidersTrait.php b/test/suite/ValuesProvidersTrait.php new file mode 100644 index 0000000..1c60173 --- /dev/null +++ b/test/suite/ValuesProvidersTrait.php @@ -0,0 +1,162 @@ + [ + '', + '', + '', + ], + 'All elements equal' => [ + 'ABC', + 'ABC', + 'ABC', + ], + 'Second string is a substring of first' => [ + 'ABCDEF', + 'BCDE', + 'BCDE', + ], + 'Basic common substring' => [ + 'ABDE', + 'ACDF', + 'A', + ], + 'Common substring of larger data set' => [ + 'ABCDEF', + 'JADFAFKDFBCDEHJDFG', + 'BCDE', + ], + 'Single element substring at start' => [ + 'ABCDEF', + 'A', + 'A', + ], + 'Single element substring at middle' => [ + 'ABCDEF', + 'D', + 'D', + ], + 'Single element substring at end' => [ + 'ABCDEF', + 'F', + 'F', + ], + 'Elements after end of first string' => [ + 'JAFAFKDBCEHJDF', + 'JDFAKDFCDEJDFG', + 'JDF', + ], + 'No common elements' => [ + 'ABC', + 'DEF', + '', + ], + 'No case common elements' => [ + 'ABC', + 'abc', + '', + ], + 'Equal Word' => [ + 'Tortor', + 'Tortor', + 'Tortor' + ], + 'Similar Word' => [ + 'Color', + 'Colour', + 'Colo' + ], + 'Word case' => [ + 'color', + 'Colour', + 'olo' + ], + 'Equal Sentence' => [ + 'Donec ullamcorper nulla non metus auctor fringilla.', + 'Donec ullamcorper nulla non metus auctor fringilla.', + 'Donec ullamcorper nulla non metus auctor fringilla.', + ], + 'Similar Sentence' => [ + 'Donec ullamcorper nulla non metus auctor fringilla.', + 'Donec ullamcorper nulla no metus auctor fringilla.', + 'Donec ullamcorper nulla no', + ], + 'Hexadecimal' => [ + '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', + '23415A32443FC243D123456789AC342553', + '123456789A', + ], + 'README example' => [ + 'BANANA', + 'ATANA', + 'ANA', + ], + ]; + } + + public function twoStringsOrderedValuesProvider() + { + return [ + 'Reverse string ASC -> DESC' => [ + 'ABCDE', + 'EDCBA', + 'A', + ], + 'Reverse string DESC -> ASC' => [ + 'EDCBA', + 'ABCDE', + 'E', + ], + 'Order change Natural -> Changed' => [ + 'ABCDEF', + 'ABDCEF', + 'AB', + ], + 'Order change Changed -> Natural' => [ + 'ABDCEF', + 'ABCDEF', + 'AB', + ], + 'Hexadecimal' => [ + '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', + '56789AB56789ABCDE56789ABCDE56789AB56789A123456789A', + '123456789A', + ], + 'Hexadecimal Reverse' => [ + '56789AB56789ABCDE56789ABCDE56789AB56789A123456789A', + '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', + '56789ABCDE', + ], + ]; + } + + public function threeStringsSymmetricValuesProvider() + { + return [ + 'No match' => [ + 'ABDEGH', + 'JKLMN', + 'OPQRST', + '', + ], + 'One match' => [ + 'ABDEGH3', + 'JKL3MN', + '3OPQRST', + '3', + ], + 'Wikipedia Example' => [ + 'ABABC', + 'BABCA', + 'ABCBA', + 'ABC', + ], + ]; + } +}