├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src └── Shorthand.php └── tests ├── ShorthandTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .DS_Store 3 | .idea 4 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | tools: 2 | external_code_coverage: true 3 | 4 | checks: 5 | php: 6 | code_rating: true 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.3 5 | - 7.2 6 | - 7.1 7 | - 7.0 8 | - 5.6 9 | - 5.5 10 | - hhvm 11 | 12 | matrix: 13 | allow_failures: 14 | - php: hhvm 15 | fast_finish: true 16 | 17 | before_script: 18 | - composer self-update 19 | - composer install 20 | 21 | script: 22 | - ./vendor/bin/phpunit --coverage-clover=coverage.clover 23 | 24 | after_script: 25 | - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi;' 26 | - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi' 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-* Kamran Ahmed 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-shorthand 2 | 3 | [![Build Status](https://travis-ci.org/kamranahmedse/php-shorthand.svg?branch=master)](https://travis-ci.org/kamranahmedse/php-shorthand) 4 | [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/kamranahmedse/php-shorthand.svg?maxAge=2592000)](https://scrutinizer-ci.com/g/kamranahmedse/php-shorthand/?branch=master) 5 | [![Latest Stable Version](https://img.shields.io/packagist/v/kamranahmedse/php-shorthand.svg?maxAge=2592000)](https://packagist.org/packages/kamranahmedse/php-shorthand) 6 | 7 | > Calculate unique shorthands for a given set of strings 8 | 9 | Inspired by [ruby's abbrev](http://apidock.com/ruby/Abbrev) module, it let's you calculate the unique set of shorthands for the given set of words. 10 | 11 | ## Installation 12 | 13 | Use the following command to install via composer 14 | 15 | ``` 16 | composer require kamranahmedse/php-shorthand 17 | ``` 18 | For further details you can find the package at Packagist. 19 | 20 | 21 | ## Usage 22 | 23 | Instantiate the `Shorthand` class while passing the words for which you want the shorthands 24 | 25 | ```php 26 | // Introduce the class into your scope 27 | use KamranAhmed\Shorthand\Shorthand; 28 | 29 | $shorthand = new Shorthand([ 30 | 'crore', 31 | 'create', 32 | ]); 33 | 34 | $shorthands = $shorthand->generate(); 35 | ``` 36 | It will return an associative array with the key set to the shorthand keyword and value set to the actual word that it refers to 37 | ```php 38 | // Shorthands for the above example 39 | [ 40 | 'cre' => 'create', 41 | 'crea' => 'create', 42 | 'creat' => 'create', 43 | 'create' => 'create', 44 | 'cro' => 'crore', 45 | 'cror' => 'crore', 46 | 'crore' => 'crore', 47 | ], 48 | ``` 49 | 50 | ## Usage Scenarios 51 | 52 | It can come quite handy when writing command line script that takes a number of options and the user may enter the options shorthand or maybe other cases where you want to be able to accept shorthands. 53 | 54 | For example, in a script that accepts the options `['delete', 'create', 'update']`, in your script, it can let you infer from the option that user wanted even when they typed a shorthand as long as it is unambiguous 55 | 56 | 57 | ```shell 58 | $ shorthand cr # create 59 | $ shorthand d # delete 60 | $ shorthand upd # update 61 | ``` 62 | 63 | ## Contribution 64 | 65 | Feel free to fork, enhance, open issues, create pull requests or spread the word. 66 | 67 | ## License 68 | 69 | MIT © [Kamran Ahmed](http://kamranahmed.info) 70 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kamranahmedse/php-shorthand", 3 | "type": "library", 4 | "description": "Calculate the set of unique abbreviations for a given set of strings.", 5 | "keywords": [ 6 | "abbrev", 7 | "ruby-abbrev", 8 | "abbreviations", 9 | "php-abbrev", 10 | "php-shorthand", 11 | "shorthand" 12 | ], 13 | "homepage": "https://github.com/kamranahmedse/php-abbrev", 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "Kamran Ahmed", 18 | "homepage": "http://kamranahmed.info", 19 | "role": "Developer" 20 | } 21 | ], 22 | "autoload": { 23 | "psr-4": { 24 | "KamranAhmed\\Shorthand\\": "src/" 25 | } 26 | }, 27 | "require": { 28 | }, 29 | "require-dev": { 30 | "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5" 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "KamranAhmed\\Shorthand\\Tests\\": "tests/" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/ 10 | 11 | 12 | 13 | 14 | src 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Shorthand.php: -------------------------------------------------------------------------------- 1 | setWords($words); 27 | } 28 | 29 | /** 30 | * @param mixed $words 31 | * 32 | * @return void 33 | */ 34 | public function setWords($words) 35 | { 36 | $this->words = !is_array($words) ? [$words] : $words; 37 | } 38 | 39 | /** 40 | * @return array 41 | * 42 | * @throws \Exception 43 | */ 44 | public function generate() 45 | { 46 | if (empty($this->words)) { 47 | throw new Exception('Word(s) are required to generate shorthands'); 48 | } 49 | 50 | // Sort them lexicographically, so that they're next to their nearest kin 51 | $words = $this->sortWordsLexico($this->words); 52 | 53 | $shorthands = []; 54 | $previous = ''; 55 | 56 | foreach ($words as $counter => $current) { 57 | 58 | // Get the next word 59 | $next = isset($words[$counter + 1]) ? $words[$counter + 1] : ""; 60 | 61 | // Two matching words found, we cannot make any unique shorthands out of this one 62 | if ($current === $next) { 63 | continue; 64 | } 65 | 66 | // Get the index at which current word differs from the next and previous words 67 | $diffPoint = $this->getDiffPoint($current, $next, $previous); 68 | 69 | $previous = $current; 70 | 71 | // End of the current word has already been reached, just put the whole word 72 | // in the shorthands and continue as it means there was no difference 73 | // between the current word and the next/previous words 74 | if ($diffPoint === strlen($current)) { 75 | $shorthands[$current] = $current; 76 | 77 | continue; 78 | } 79 | 80 | // Generate the shorthands by creating substring from the diff point to the end 81 | while ($diffPoint <= strlen($current)) { 82 | $shorthand = substr($current, 0, $diffPoint); 83 | $shorthands[$shorthand] = $current; 84 | 85 | $diffPoint++; 86 | } 87 | } 88 | 89 | return $shorthands; 90 | } 91 | 92 | /** 93 | * Gets the point at which current word differs from the next and previous words 94 | * 95 | * @param string $current 96 | * @param string $next 97 | * @param string $previous 98 | * 99 | * @return int 100 | */ 101 | protected function getDiffPoint($current, $next, $previous) 102 | { 103 | $nextCharMatches = true; 104 | $prevCharMatches = true; 105 | 106 | $currentLength = strlen($current); 107 | 108 | // Iterate through each character of the current word 109 | // and find the point where it differs from the previous and next word 110 | for ($diffPoint = 0; $diffPoint < $currentLength; $diffPoint++) { 111 | 112 | $currChar = $current[$diffPoint]; 113 | $nextChar = !empty($next[$diffPoint]) ? $next[$diffPoint] : ''; 114 | $prevChar = !empty($previous[$diffPoint]) ? $previous[$diffPoint] : ''; 115 | 116 | $nextCharMatches = $nextCharMatches && $currChar === $nextChar; 117 | $prevCharMatches = $prevCharMatches && $currChar === $prevChar; 118 | 119 | // If neither of the next and previous word characters match, 120 | // we have found ourselves the point at which both the words differ 121 | if (!$nextCharMatches && !$prevCharMatches) { 122 | $diffPoint++; 123 | 124 | break; 125 | } 126 | } 127 | 128 | return $diffPoint; 129 | } 130 | 131 | /** 132 | * Sorts the words lexicographically in order to bring the words closer to their kin 133 | * 134 | * @param array $words 135 | * 136 | * @return array 137 | */ 138 | protected function sortWordsLexico($words) 139 | { 140 | usort($words, function ($a, $b) { 141 | if ($a === $b) { 142 | return 0; 143 | } 144 | 145 | return $a > $b ? 1 : -1; 146 | }); 147 | 148 | return $words; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /tests/ShorthandTest.php: -------------------------------------------------------------------------------- 1 | shorthand = new Shorthand(); 24 | } 25 | 26 | /** 27 | * @dataProvider shorthandsProvider 28 | * 29 | * @param $words 30 | * @param $expectedShorthands 31 | */ 32 | public function testCanGenerateShorthands($words, $expectedShorthands) 33 | { 34 | $this->shorthand->setWords($words); 35 | $shorthands = $this->shorthand->generate(); 36 | 37 | $this->assertEquals($expectedShorthands, $shorthands); 38 | } 39 | 40 | /** 41 | * @expectedException \Exception 42 | * @expectedExceptionMessage Word(s) are required to generate shorthands 43 | */ 44 | public function testThrowsExceptionForEmptyWords() 45 | { 46 | $this->shorthand->setWords([]); 47 | $this->shorthand->generate(); 48 | } 49 | 50 | /** 51 | * @return array 52 | */ 53 | public function shorthandsProvider() 54 | { 55 | return [ 56 | [ 57 | ['create', 'crore'], 58 | [ 59 | 'cre' => 'create', 60 | 'crea' => 'create', 61 | 'creat' => 'create', 62 | 'create' => 'create', 63 | 'cro' => 'crore', 64 | 'cror' => 'crore', 65 | 'crore' => 'crore', 66 | ], 67 | ], 68 | [ 69 | ['crore', 'create', 'crop'], 70 | [ 71 | 'cror' => 'crore', 72 | 'crore' => 'crore', 73 | 'cre' => 'create', 74 | 'crea' => 'create', 75 | 'creat' => 'create', 76 | 'create' => 'create', 77 | 'crop' => 'crop', 78 | ], 79 | ], 80 | [ 81 | ['create', 'crore', 'create', 'crore'], 82 | [ 83 | 'cre' => 'create', 84 | 'crea' => 'create', 85 | 'creat' => 'create', 86 | 'create' => 'create', 87 | 'cro' => 'crore', 88 | 'cror' => 'crore', 89 | 'crore' => 'crore', 90 | ], 91 | ], 92 | [ 93 | ['ruby', 'rules'], 94 | [ 95 | 'rub' => 'ruby', 96 | 'ruby' => 'ruby', 97 | 'rul' => 'rules', 98 | 'rule' => 'rules', 99 | 'rules' => 'rules', 100 | ], 101 | ], 102 | [ 103 | 'ruby', 104 | [ 105 | 'r' => 'ruby', 106 | 'ru' => 'ruby', 107 | 'rub' => 'ruby', 108 | 'ruby' => 'ruby', 109 | ], 110 | ], 111 | ]; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |