diff --git a/readme.md b/readme.md index e321ad6d80..08f141bb55 100644 --- a/readme.md +++ b/readme.md @@ -74,6 +74,8 @@ Each of the generator properties (like `name`, `address`, and `lorem`) are calle randomLetter // 'b' randomElements($array = array ('a','b','c'), $count = 1) // array('c') randomElement($array = array ('a','b','c')) // 'b' + shuffle('hello, world') // 'rlo,h eoldlw' + shuffle(array(1, 2, 3)) // array(2, 1, 3) numerify($string = '###') // '609' lexify($string = '????') // 'wgts' bothify($string = '## ??') // '42 jz' diff --git a/src/Faker/Provider/Base.php b/src/Faker/Provider/Base.php index b22d527b3f..de61c8b49a 100644 --- a/src/Faker/Provider/Base.php +++ b/src/Faker/Provider/Base.php @@ -206,6 +206,100 @@ public static function randomKey($array = array()) return $key; } + /** + * Returns a shuffled version of the argument. + * + * This function accepts either an array, or a string. + * + * @example $faker->shuffle([1, 2, 3]); // [2, 1, 3] + * @example $faker->shuffle('hello, world'); // 'rlo,h eold!lw' + * + * @see shuffleArray() + * @see shuffleString() + * + * @param array|string $arg The set to shuffle + * @return array|string The shuffled set + */ + public static function shuffle($arg = '') + { + if (is_array($arg)) { + return static::shuffleArray($arg); + } + if (is_string($arg)) { + return static::shuffleString($arg); + } + throw new \InvalidArgumentException('shuffle() only supports strings of arrays'); + } + + /** + * Returns a shuffled version of the array. + * + * This function does not mutate the original array. It uses the + * Fisher–Yates algorithm, which is unbiaised, together with a Mersenne + * twister random generator. This function is therefore more random than + * PHP's shuffle() function, and it is seedable. + * + * @link http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + * + * @example $faker->shuffleArray([1, 2, 3]); // [2, 1, 3] + * + * @param array $array The set to shuffle + * @return array The shuffled set + */ + public static function shuffleArray($array = array()) + { + $shuffledArray = array(); + $i = 0; + reset($array); + while (list($key, $value) = each($array)) { + if ($i == 0) { + $j = 0; + } else { + $j = mt_rand(0, $i); + } + if ($j == $i) { + $shuffledArray[]= $value; + } else { + $shuffledArray[]= $shuffledArray[$j]; + $shuffledArray[$j] = $value; + } + $i++; + } + return $shuffledArray; + } + + /** + * Returns a shuffled version of the string. + * + * This function does not mutate the original string. It uses the + * Fisher–Yates algorithm, which is unbiaised, together with a Mersenne + * twister random generator. This function is therefore more random than + * PHP's shuffle() function, and it is seedable. Additionally, it is + * UTF8 safe if the mb extension is available. + * + * @link http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + * + * @example $faker->shuffleString('hello, world'); // 'rlo,h eold!lw' + * + * @param string $string The set to shuffle + * @param string $encoding The string encoding (defaults to UTF-8) + * @return string The shuffled set + */ + public static function shuffleString($string = '', $encoding = 'UTF-8') + { + if (function_exists('mb_strlen')) { + // UTF8-safe str_split() + $array = array(); + $strlen = mb_strlen($string, $encoding); + for ($i = 0; $i < $strlen; $i++) { + $array []= mb_substr($string, $i, 1, $encoding); + } + } else { + $array = str_split($string, 1); + } + return join('', static::shuffleArray($array)); + } + /** * Replaces all hash sign ('#') occurrences with a random number * Replaces all percentage sign ('%') occurrences with a not null number diff --git a/test/Faker/Provider/BaseTest.php b/test/Faker/Provider/BaseTest.php index cba0a1ebaa..875d19db6a 100644 --- a/test/Faker/Provider/BaseTest.php +++ b/test/Faker/Provider/BaseTest.php @@ -135,6 +135,96 @@ public function testRandomElementReturnsElementFromAssociativeArray() $this->assertContains(BaseProvider::randomElement($elements), $elements); } + public function testShuffleReturnsStringWhenPassedAStringArgument() + { + $this->assertInternalType('string', BaseProvider::shuffle('foo')); + } + + public function testShuffleReturnsArrayWhenPassedAnArrayArgument() + { + $this->assertInternalType('array', BaseProvider::shuffle(array(1, 2, 3))); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testShuffleThrowsExceptionWhenPassedAnInvalidArgument() + { + BaseProvider::shuffle(false); + } + + public function testShuffleArraySupportsEmptyArrays() + { + $this->assertEquals(array(), BaseProvider::shuffleArray(array())); + } + + public function testShuffleArrayReturnsAnArrayOfTheSameSize() + { + $array = array(1, 2, 3, 4, 5); + $this->assertSameSize($array, BaseProvider::shuffleArray($array)); + } + + public function testShuffleArrayReturnsAnArrayWithSameElements() + { + $array = array(2, 4, 6, 8, 10); + $shuffleArray = BaseProvider::shuffleArray($array); + $this->assertContains(2, $shuffleArray); + $this->assertContains(4, $shuffleArray); + $this->assertContains(6, $shuffleArray); + $this->assertContains(8, $shuffleArray); + $this->assertContains(10, $shuffleArray); + } + + public function testShuffleArrayReturnsADifferentArrayThanTheOriginal() + { + $arr = array(1, 2, 3, 4, 5); + $shuffledArray = BaseProvider::shuffleArray($arr); + $this->assertNotEquals($arr, $shuffledArray); + } + + public function testShuffleArrayLeavesTheOriginalArrayUntouched() + { + $arr = array(1, 2, 3, 4, 5); + BaseProvider::shuffleArray($arr); + $this->assertEquals($arr, array(1, 2, 3, 4, 5)); + } + + public function testShuffleStringSupportsEmptyStrings() + { + $this->assertEquals('', BaseProvider::shuffleString('')); + } + + public function testShuffleStringReturnsAnStringOfTheSameSize() + { + $string = 'abcdef'; + $this->assertEquals(strlen($string), strlen(BaseProvider::shuffleString($string))); + } + + public function testShuffleStringReturnsAnStringWithSameElements() + { + $string = 'acegi'; + $shuffleString = BaseProvider::shuffleString($string); + $this->assertContains('a', $shuffleString); + $this->assertContains('c', $shuffleString); + $this->assertContains('e', $shuffleString); + $this->assertContains('g', $shuffleString); + $this->assertContains('i', $shuffleString); + } + + public function testShuffleStringReturnsADifferentStringThanTheOriginal() + { + $string = 'abcdef'; + $shuffledString = BaseProvider::shuffleString($string); + $this->assertNotEquals($string, $shuffledString); + } + + public function testShuffleStringLeavesTheOriginalStringUntouched() + { + $string = 'abcdef'; + BaseProvider::shuffleString($string); + $this->assertEquals($string, 'abcdef'); + } + public function testNumerifyReturnsSameStringWhenItContainsNoHashSign() { $this->assertEquals('fooBar?', BaseProvider::numerify('fooBar?'));