diff --git a/README.md b/README.md index ebdab65..e9ad6d6 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Note: The methods listed below are subject to change until we reach a 1.0.0 rele * [shuffle](#shuffle) * [longestCommonPrefix](#longestcommonprefix) * [longestCommonSuffix](#longestcommonsuffix) + * [longestCommonSubstring](#longestcommonsubstring) * [Tests](#tests) * [License](#license) @@ -543,9 +544,21 @@ S::create('fòô bàř', 'UTF-8')->longestCommonSuffix('fòr bàř'); S::longestCommonSuffix('fòô bàř', 'fòr bàř', 'UTF-8'); // ' bàř' ``` -## TODO +##### longestCommonSubstring -**longestCommonSubstring** +$stringy->longestCommonSubstring(string $otherString) + +S::longestCommonSubstring(string $str, string $otherString [, $encoding ]) + +Finds the longest common substring between $str and $otherString. In the +case of ties, returns that which occurs first. + +```php +S::create('foo bar')->longestCommonSubstring('boo far'); +S::longestCommonSubstring('foo bar', 'boo far'); // 'oo ' +``` + +## TODO **count** => substr_count diff --git a/src/Stringy/StaticStringy.php b/src/Stringy/StaticStringy.php index 87c73a0..2d1ece7 100644 --- a/src/Stringy/StaticStringy.php +++ b/src/Stringy/StaticStringy.php @@ -405,7 +405,7 @@ class StaticStringy * * @return string The longest common prefix */ - public function longestCommonPrefix($str, $otherString, $encoding = null) + public static function longestCommonPrefix($str, $otherString, $encoding = null) { return Stringy::create($str, $encoding) ->longestCommonPrefix($otherString)->str; @@ -416,9 +416,22 @@ class StaticStringy * * @return string The longest common suffix */ - public function longestCommonSuffix($str, $otherString, $encoding = null) + public static function longestCommonSuffix($str, $otherString, $encoding = null) { return Stringy::create($str, $encoding) ->longestCommonSuffix($otherString)->str; } + + /** + * Finds the longest common substring between $str and $otherString. In the + * case of ties, returns that which occurs first. + * + * @return string The longest common substring + */ + public static function longestCommonSubstring($str, $otherString, + $encoding = null) + { + return Stringy::create($str, $encoding) + ->longestCommonSubstring($otherString)->str; + } } diff --git a/src/Stringy/Stringy.php b/src/Stringy/Stringy.php index b2604bf..821939f 100644 --- a/src/Stringy/Stringy.php +++ b/src/Stringy/Stringy.php @@ -677,4 +677,49 @@ class Stringy return $this; } + + /** + * Finds the longest common substring between $str and $otherString. In the + * case of ties, returns that which occurs first. + * + * @return Stringy Object with its $str being the longest common substring + */ + public function longestCommonSubstring($otherString) + { + // Uses dynamic programming to solve + // http://en.wikipedia.org/wiki/Longest_common_substring_problem + $strLength = mb_strlen($this->str, $this->encoding); + $otherLength = mb_strlen($otherString, $this->encoding); + + // Return if either string is empty + if ($strLength == 0 || $otherLength == 0) { + $this->str = ''; + return $this; + } + + $len = 0; + $end = 0; + $table = array_fill(0, $strLength + 1, array_fill(0, $otherLength + 1, 0)); + + for ($i = 1; $i <= $strLength; $i++){ + for ($j = 1; $j <= $otherLength; $j++){ + $strChar = mb_substr($this->str, $i - 1, 1, $this->encoding); + $otherChar = mb_substr($otherString, $j - 1, 1, $this->encoding); + + if ($strChar == $otherChar) { + $table[$i][$j] = $table[$i - 1][$j - 1] + 1; + if ($table[$i][$j] > $len) { + $len = $table[$i][$j]; + $end = $i; + } + } else { + $table[$i][$j] = 0; + } + } + } + + $this->str = mb_substr($this->str, $end - $len, $len, $this->encoding); + + return $this; + } } diff --git a/tests/Stringy/CommonTest.php b/tests/Stringy/CommonTest.php index 76e8ba9..97d9f5c 100644 --- a/tests/Stringy/CommonTest.php +++ b/tests/Stringy/CommonTest.php @@ -499,6 +499,24 @@ class CommonTest extends PHPUnit_Framework_TestCase return $testData; } + public function stringsForLongestCommonSubstring() + { + $testData = array( + array('foo', 'foobar', 'foo bar'), + array('foo bar', 'foo bar', 'foo bar'), + array('oo ', 'foo bar', 'boo far'), + array('foo ba', 'foo bad', 'foo bar'), + array('', 'foo bar', ''), + array('fòô', 'fòôbàř', 'fòô bàř', 'UTF-8'), + array('fòô bàř', 'fòô bàř', 'fòô bàř', 'UTF-8'), + array(' bàř', 'fòô bàř', 'fòr bàř', 'UTF-8'), + array(' ', 'toy car', 'fòô bàř', 'UTF-8'), + array('', 'fòô bàř', '', 'UTF-8'), + ); + + return $testData; + } + // A test is required so as not to throw an error // This is a lot cleaner than using PHPUnit's mocks to spy public function test() { diff --git a/tests/Stringy/StaticStringyTest.php b/tests/Stringy/StaticStringyTest.php index 73bc482..8691fa1 100644 --- a/tests/Stringy/StaticStringyTest.php +++ b/tests/Stringy/StaticStringyTest.php @@ -288,4 +288,14 @@ class StaticStringyTestCase extends CommonTest $result = S::longestCommonSuffix($str, $otherString, $encoding); $this->assertEquals($expected, $result); } + + /** + * @dataProvider stringsForLongestCommonSubstring + */ + public function testLongestCommonSubstring($expected, $str, $otherString, + $encoding = null) + { + $result = S::longestCommonSubstring($str, $otherString, $encoding); + $this->assertEquals($expected, $result); + } } diff --git a/tests/Stringy/StringyTest.php b/tests/Stringy/StringyTest.php index ba49bef..ca19e9d 100644 --- a/tests/Stringy/StringyTest.php +++ b/tests/Stringy/StringyTest.php @@ -281,13 +281,13 @@ class StringyTestCase extends CommonTest } /** - * @dataProvider stringsForLongestCommonSuffix + * @dataProvider stringsForLongestCommonSubstring */ - public function testLongestCommonSuffix($expected, $str, $otherString, - $encoding = null) + public function testLongestCommonSubstring($expected, $str, $otherString, + $encoding = null) { $result = S::create($str, $encoding) - ->longestCommonSuffix($otherString); + ->longestCommonSubstring($otherString); $this->assertEquals($expected, $result); } }