I am trying to create a function to generate all the possible combinations of a particular group of characters, and have it variable based on length.
I have a function to create an array of the characters I would like, and this works fine.
function generate_characters($l, $n, $d) {
$r = array();
if ($l === true) {
foreach (range('a', 'z') as $index) {
array_push($r, $index);
}
}
if ($n === true) { array_push($r, '0','1','2','3','4','5','6','7','8','9'); }
if ($d === true) { array_push($r, '-'); }
return $r;
}
I then need to have it create an array of all possible combinations based on $length, for example if '$length = 1' I need the following array
Array
(
[0] => a
[1] => b
[2] => c
[3] => d
[4] => e
[5] => f
[6] => g
[7] => h
[8] => i
[9] => j
[10] => k
[11] => l
[12] => m
[13] => n
[14] => o
[15] => p
[.... removed some values to save on length ....]
[35] => 9
)
but if '$length = 2' I need this
Array
(
[0] => aa
[1] => ab
[2] => ac
[3] => ad
[4] => ae
[5] => af
[6] => ag
[7] => ah
[8] => ai
[9] => aj
[.... removed some values to save on length ....]
[1329] => 97
[1330] => 98
[1331] => 99
)
I have tried array_walk() and array_walk_recursive(), along with several foreach and while loops, to no avail.
I can get it to work by manually doing it for each length, but not with a variable length by doing this, but don't know how to make it variable by length.
function generate_two($l, $n, $d) {
$r = array();
foreach (generate_characters($l, $n, false) as $v1) {
foreach (generate_characters($l, $n, $d) as $v2) {
array_push($results, "$v1$v2");
}
}
return $r;
}
all this whilst, not having the '-' as the first character, although I could remove those values after generating the array if I needed to.
Thanks, Dan
Presuming you want to use the array you created as the array to use to append to. I can't see why you'd need to work with specific characters other than that in the array (I may be wrong, but this can be easily adapted to cater for that).
/**
* #param array $array
* #param $length
* #param null $original
* #return array
*/
function generate_values(array $array, $length, $original = null) {
// If length is 1 or less just return the array
if ($length <= 1) {
return $array;
}
// The resulting values array
$result = [];
// Copy the array if original doesn't exist
if (!is_array($original)) {
$original = $array;
}
// Loop over each item and append the original values
foreach($array as $item) {
foreach($original as $character) {
$result[] = $item . $character;
};
}
// Recursively generate values until the length is 1
return generate_values($result, --$length, $original);
}
To use it you can use your generator.
$characterArray = generate_characters(true, false, false);
$results = generate_values($characterArray, 2);
I don't know if this is the best solution but :
function generate_characters($length, $n, $d, $array = array()) {
$letters = array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9');
$numbers = range(0, 9);
$others = array('-');
if($n == true)
$letters = array_merge($letters, $numbers);
if($d == true)
$letters = array_merge($letters, $others);
if(empty($array))
$array = $letters;
if(--$length <= 0)
return $array;
$result = array();
foreach ($array as $value) {
foreach ($letters as $add) {
$result[] = $value.$add;
}
}
return generate_characters($length, $n, $d, $result);
}
echo '<pre>';
print_r(generate_characters(3, true, true));
Sample code
function generate_characters($l, $n, $d)
{
// Start with an empty list
$r = array();
// Add the letters, if required
if ($l) {
$r = array_merge($r, range('a', 'z'));
}
// Add the digits, if required
if ($n) {
$r = array_merge($r, range('0', '9'));
}
// Add special characters, if required
if ($d) {
$r[] = '-';
}
return $r;
}
/**
* Generate all combinations of $len characters using the characters
* from a given list
*
* #param array $chars the list of characters to use
* #param int $len the length of strings to generate
* #return array
*/
function generate_combinations(array $chars, $len)
{
// $len <= 0: this is an error
// $len == 1: the input list is the output
if ($len <= 1) {
return $chars;
}
// Compute the output here
$result = array();
// Recursively compute the list of characters of length $len - 1
$partial = generate_combinations($chars, $len - 1);
// Append each entry from the input list to each string of length $len - 1 computed on the previous step
foreach ($partial as $head) {
foreach ($chars as $tail) {
// Put the combination in the output list
$result[] = $head.$tail;
}
}
// This is the list of all strings of length $len over the list $chars
return $result;
}
Usage
There is no need to call generate_characters() again and again. Given the same arguments, it always returns the same list of characters. Save it in a variable, use the variable further.
$chars = generate_characters(TRUE, FALSE, TRUE);
$comb2 = generate_combinations($chars, 2);
$comb5 = generate_combinations($chars, 5);
Remarks
While theoretically correct, generating all the combinations is completely useless in real life. The number of combinations increases exponentially and soon your script will use all the available memory to store the list.
I tested the code above and PHP 5.6 needed more than 2 GB of memory to generate all the combinations of 5 characters (using letters + dash). PHP 7 uses memory better and it required less than 1 GB of memory for the same task.
As you can imagine, it's impossible to use higher values for parameter $len; even for $len == 5, the amounts listed above are already huge.
If you want to combination of two array's value you can use directly this code. You does not required length of that array in your function.
function generateValue($array1, $array2) {
$result = array();
foreach ($array as $value) {
foreach ($original as $value2) {
$result[] = $value.$value2;
}
}
return $result;
}
print_r(generateValue($array1, $array2));
Related
I have an array like this. I want to retreive the array element with highest version number.
Array(
[0] => "ARRELEMENT_test",
[1] => "ARRELEMENT_test_v",
[2] => "ARRELEMENT_test_v5",
[3] => "ARRELEMENT_test_v1",
[4] => "ARRELEMENT_test_v2",
[5] => "ARRELEMENT_test_v3"
)
Here it should return ARRELEMENT_test_v3 array element. Is there are any predefined function so that we can achieve this. I am trying like this.
function findPrefix($array) {
$i = 1;
while ($i < strlen($array[0])) {
$match = substr($array[0], 0, $i);
foreach ($array as $arr) {
$nextmatch = substr($arr, 0, $i-1);
if ($match < $nextmatch ) {
//return array element with highest prefix
}
}
$i++; // increase string length
}
}
function getIndexOfMaxNumber($arr){
//get the numeric value from the string and store it in an array
foreach($arr as $key=>$val){
preg_match('!\d+!', $val, $match);
if($match){
$index[$key]= $match[0];
}
}
//Sort an array and maintain index association
arsort($index);
//Set the internal pointer of an array to its first element
reset($index);
$first_key = key($index);
return $first_key;
}
$maxIndex = getIndexOfMaxNumber($arr);
echo $arr[$maxIndex];
Demo
I have array symbols what I want replace, but I need generate all possibillity
$lt = array(
'a' => 'ą',
'e' => 'ę',
'i' => 'į',
);
For example if I have this string:
tazeki
There can be huge amount of results:
tązeki
tazęki
tązęki
tazekį
tązekį
tazękį
tązękį
My question is what formula use to have all variants ?
This should work for you, easy and simple:
What does this code do?
1. Data part
In the data part I just define the string and the replacement's for the single character with a associative array (search character as key, replacement as value).
2. getReplacements() function
This function get's all combinations of the characters which have to be replaced in this format:
key = index in the string
value = character
So in this code example the array would look something like this:
Array (
[0] => Array (
[1] => a
)
[1] => Array (
[3] => e
)
[2] => Array (
[3] => e
[1] => a
)
[3] => Array (
[5] => i
)
[4] => Array (
[5] => i
[1] => a
)
[5] => Array (
[5] => i
[3] => e
)
[6] => Array (
[5] => i
[3] => e
[1] => a
)
)
As you can see this array holds all combinations of the characters which have to be replaced, in this format:
[0] => Array (
//^^^^^ The entire sub array is the combination which holds the single characters which will be replaced
[1] => a
//^ ^ A single character of the full combination which will be replaced
//| The index of the character in the string (This is that it also works if you have a character multiple times in your string)
// e.g. 1 -> t *a* z e k i
// ^ ^ ^ ^ ^ ^
// | | | | | |
// 0 *1* 2 3 4 5
)
So how does it gets all combinations?
Pretty simple I loop through every single character which I want to replace with a foreach loop and then I go through every single combination which I already have and combine it with the character which is currently the value of the foreach loop.
But to get this to work you have to start with a empty array. So as a simple example to see and understand what I mean:
Characters which have to be replaced (Empty array is '[]'): [1, 2, 3]
//new combinations for the next iteration
|
Character loop for NAN*:
Combinations:
- [] | -> []
Character loop for 1:
Combinations:
- [] + 1 | -> [1]
Character loop for 2:
Combinations:
- [] + 2 | -> [2]
- [1] + 2 | -> [1,2]
Character loop for 3:
Combinations:
- [] + 3 | -> [3]
- [1] + 3 | -> [1,3]
- [2] + 3 | -> [2,3]
- [1,2] + 3 | -> [1,2,3]
//^ All combinations here
* NAN: not a number
So as you can see there is always: (2^n)-1 combinations in total. Also from this method there is a empty array left in the combination array, so before I return the array I just use array_filter() to remove all empty arrays and array_values() to reindex the entire array.
3. Replacement part
So to get all characters from the string where will build the combinations out of I use this line:
array_intersect(str_split($str), array_keys($replace))
This just get's all coincidences with array_intersect() from the string as array with str_split() and the keys from the replace array with array_keys().
In this code the array which you pass to the getReplacements() function would look something like this:
Array
(
[1] => a
//^ ^ The single character which is in the string and also in the replace array
//| Index in the string from the character
[3] => e
[5] => i
)
4. Replace all combinations
At the end you only have to replace all combinations in the source string with the replace array. For this I loop just through every combination and replace every single character in the string from the combination with the matching character from the replace array.
This can be simply done with this line:
$tmp = substr_replace($tmp, $replace[$v], $k, 1);
//^^^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^ ^ Length of the replacement
//| | | Index from the string, where it should replace
//| | Get the replaced character to replace it
//| Replaces every single character one by one in the string
For more information about substr_replace() see the manual: http://php.net/manual/en/function.substr-replace.php
After this line you just add the replaced string in the result array and rest the string to the source string again.
Code:
<?php
//data
$str = "tazeki";
$replace = array(
'a' => 'ą',
'e' => 'ę',
'i' => 'į',
);
function getReplacements($array) {
//initalize array
$results = [[]];
//get all combinations
foreach ($array as $k => $element) {
foreach ($results as $combination)
$results[] = [$k => $element] + $combination;
}
//return filtered array
return array_values(array_filter($results));
}
//get all combinations to replace
$combinations = getReplacements(array_intersect(str_split($str), array_keys($replace)));
//replace all combinations
foreach($combinations as $word) {
$tmp = $str;
foreach($word as $k => $v)
$tmp = substr_replace($tmp, $replace[$v], $k, 1);
$result[] = $tmp;
}
//print data
print_r($result);
?>
Output:
Array
(
[0] => tązeki
[1] => tazęki
[2] => tązęki
[3] => tazekį
[4] => tązekį
[5] => tazękį
[6] => tązękį
)
Here is a solution particularly for your task. You can pass any word and any array for replacements, it should work.
<?php
function getCombinations($word, $charsReplace)
{
$charsToSplit = array_keys($charsReplace);
$pattern = '/('.implode('|', $charsToSplit).')/';
// split whole word into parts by replacing symbols
$parts = preg_split($pattern, $word, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
$replaceParts = array();
$placeholder = '';
// create string with placeholders (%s) for sptrinf and array of replacing symbols
foreach ($parts as $wordPart) {
if (isset($charsReplace[$wordPart])) {
$replaceParts[] = $wordPart;
$placeholder .= '%s';
} else {
$placeholder .= $wordPart;
}
}
$paramsCnt = count($replaceParts);
$combinations = array();
$combinationsCnt = pow(2, $paramsCnt);
// iterate all combinations (with help of binary codes)
for ($i = 0; $i < $combinationsCnt; $i++) {
$mask = sprintf('%0'.$paramsCnt.'b', $i);
$sprintfParams = array($placeholder);
foreach ($replaceParts as $index => $char) {
$sprintfParams[] = $mask[$index] == 1 ? $charsReplace[$char] : $char;
}
// fill current combination into placeholder and collect it in array
$combinations[] = call_user_func_array('sprintf', $sprintfParams);
}
return $combinations;
}
$lt = array(
'a' => 'ą',
'e' => 'ę',
'i' => 'į',
);
$word = 'stazeki';
$combinations = getCombinations($word, $lt);
print_r($combinations);
// Оutput:
// Array
// (
// [0] => stazeki
// [1] => stazekį
// [2] => stazęki
// [3] => stazękį
// [4] => stązeki
// [5] => stązekį
// [6] => stązęki
// [7] => stązękį
// )
This is an implementation in PHP :
<?php
/**
* String variant generator
*/
class stringVariantGenerator
{
/**
* Contains assoc of char => array of all its variations
* #var array
*/
protected $_mapping = array();
/**
* Class constructor
*
* #param array $mapping Assoc array of char => array of all its variation
*/
public function __construct(array $mapping = array())
{
$this->_mapping = $mapping;
}
/**
* Generate all variations
*
* #param string $string String to generate variations from
*
* #return array Assoc containing variations
*/
public function generate($string)
{
return array_unique($this->parseString($string));
}
/**
* Parse a string and returns variations
*
* #param string $string String to parse
* #param int $position Current position analyzed in the string
* #param array $result Assoc containing all variations
*
* #return array Assoc containing variations
*/
protected function parseString($string, $position = 0, array &$result = array())
{
if ($position <= strlen($string) - 1)
{
if (isset($this->_mapping[$string{$position}]))
{
foreach ($this->_mapping[$string{$position}] as $translatedChar)
{
$string{$position} = $translatedChar;
$this->parseString($string, $position + 1, $result);
}
}
else
{
$this->parseString($string, $position + 1, $result);
}
}
else
{
$result[] = $string;
}
return $result;
}
}
// This is where you define what are the possible variations for each char
$mapping = array(
'e' => array('#', '_'),
'p' => array('*'),
);
$word = 'Apple love!';
$generator = new stringVariantGenerator($mapping);
print_r($generator->generate($word));
It would return :
Array
(
[0] => A**l# lov#!
[1] => A**l# lov_!
[2] => A**l_ lov#!
[3] => A**l_ lov_!
)
In your case, if you want to use the letter itself as a valid translated value, just add it into the array.
$lt = array(
'a' => array('a', 'ą'),
'e' => array('e', 'ę'),
'i' => array('i', 'į'),
);
I'm not sure if you can do this with keys and value but as two arrays definatley.
$find = array('ą','ę','į');
$replace = array('a', 'e', 'i');
$string = 'tązekį';
echo str_replace($find, $replace, $string);
I'm not sure If I understand your question, but here is my answer :-)
$word = 'taxeki';
$word_arr = array();
$word_arr[] = $word;
//Loop through the $lt-array where $key represents what char to search for
//$letter what to replace with
//
foreach($lt as $key=>$letter) {
//Loop through each char in the $word-string
for( $i = 0; $i <= strlen($word)-1; $i++ ) {
$char = substr( $word, $i, 1 );
//If current letter in word is same as $key from $lt-array
//then add a word the $word_arr where letter is replace with
//$letter from the $lt-array
if ($char === $key) {
$word_arr[] = str_replace($char, $letter, $word);
}
}
}
var_dump($word_arr);
I'm assuming you have a known number of elements in your array, and I am assuming that that number is 3. You will have to have additional loops if you have additional elements in your $lt array.
$lt = array(
'a' => array('a', 'x'),
'e' => array('e', 'x'),
'i' => array('i', 'x')
);
$str = 'tazeki';
foreach ($lt['a'] as $a)
foreach ($lt['e'] as $b)
foreach ($lt['i'] as $c) {
$newstr = str_replace(array_keys($lt), array($a, $b, $c), $str);
echo "$newstr<br />\n";
}
If the number of elements in $lt is unknown or variable then this is not a good solution.
Well, though #Rizier123 and others have already provided good answers will clear explanations, I would like to leave my contribution as well. This time, honoring the Way of the Short Source Code over readability ... ;-)
$lt = array('a' => 'ą', 'e' => 'ę', 'i' => 'į');
$word = 'tazeki';
for ($i = 0; $i < strlen($word); $i++)
$lt[$word[$i]] && $r[pow(2, $u++)] = [$lt[$word[$i]], $i];
for ($i = 1; $i < pow(2, count($r)); $i++) {
for ($w = $word, $u = end(array_keys($r)); $u > 0; $u >>= 1)
($i & $u) && $w = substr_replace($w, $r[$u][0], $r[$u][1], 1);
$res[] = $w;
}
print_r($res);
Output:
Array
(
[0] => tązeki
[1] => tazęki
[2] => tązęki
[3] => tazekį
[4] => tązekį
[5] => tazękį
[6] => tązękį
)
After CURL'ing a page and creating a list of items in an array. I have no control over the markup. I'm trying to filter out the last remainder of items that don't follow the alphabet after the letter Z.
In this case I'd like to disregard indexes 7 and beyond.
Array
(
[0] => Apple
[1] => Acorn
[2] => Banana
[3] => Cucumber
[4] => Date
[5] => Zombify
[6] => Zoo // last item
[7] => Umbrella // disregard
[8] => Kangaroo // disregard
[9] => Apple // disregard
[10] => Star // disregard
[11] => Umbrella // disregard
[12] => Kangaroo // disregard
[13] => Apple // disregard
)
What I cannot figure out is the appropriate solution for the cutoff point at the letter Z.
$letters = range('A', 'Z');
foreach($listContent as $listItem) {
foreach($letters as $letter) {
if (substr($listItem, 0, 1) == $letter) {
$newArray[] = $listItem;
}
}
}
How about this:
$last= "A";
foreach($listContent as $listItem) {
$current = strtoupper(substr($listItem, 0,1));
// only add the element if it is alphabetically sorted behind the last element and is a character
if ($current < $last || $current < 'A' || $current > 'Z') break;
$last = $current;
$newArray[] = $listItem;
}
This will handle the updated array you posted. For instance, it will keep Zombie and Zoo, but then it will end.
$letters = range('A', 'Z');
$newArray = array();
foreach ($listContent as $listItem) {
$firstLetter = substr($listItem, 0, 1);
if (!in_array($firstLetter, $letters)) {
break; // edited to break instead of continue.
}
$position = array_search($firstLetter, $letters);
$letters = array_slice($letters, $position);
$newArray[] = $listItem;
}
Have tested this and works correctly - to my understanding of your requirement:
$letters = range('A', 'Z');
$item_prev_char = "";
foreach( $listContent as $listItem )
{
$item_first_char = substr($listItem, 0, 1);
if ( $item_prev_char <= $item_first_char )
{
foreach( $letters as $letter )
{
if ($item_first_char == $letter )
{
$new_array[] = $listItem;
$item_prev_char = $item_first_char;
echo $listItem."<br/>";
break;
}
}
}
}
Hope everyone is doing well...
I am looking for something similar to array_chunk() but using an empty value as the separator?
I have -
Array
(
[0] => /some/path:
[1] => file.csv
[2] => file.dat
[3] =>
[4] => /some/other/path:
[5] => file.csv
[6] => file.csv.gz
[7] => file.dat
[8] =>
[9] => /some/other/other/path:
[10] => file.csv
[11] => file.dat
)
And would like to achieve -
Array
(
[0] => Array
(
[0] => /some/path:
[1] => file.csv
[2] => file.dat
)
[1] => Array
(
[0] => /some/other/path:
[1] => file.csv
[2] => file.csv.gz
[3] => file.dat
)
[2] => Array
(
[0] => /some/other/other/path:
[1] => file.csv
[2] => file.dat
)
)
Now I cant just chunk on every 3 as you can see some locations will have more than 1 file.
I can achieve via loop and counter but I figured there would be a cleaner method?
Thanks
Best way I can think of is this loop which I think is pretty clean:
/**
* #param array $inputArr The array you wish to split.
* #param string $splitStr The string you wish to split by.
*
* #return array
*/
function split_arrays($inputArr, $splitStr) {
$outputArr = array();
$i = 0;
foreach ($inputArr as $data) {
if ($data == $splitStr) {
$i++;
continue;
}
$outputArr[$i][] = $data;
}
return $outputArr;
}
The other way I thought of also uses loops but is not as clean. It searches for the index of the next split string and breaks off that chunk.
/**
* #param array $inputArr The array you wish to split.
* #param string $splitStr The string you wish to split by.
*
* #return array
*/
function split_arrays2($inputArr,$splitStr) {
$outputArr = array(); $i=0;
while(count($inputArr)) {
$index = array_search($splitStr,$inputArr)
if($index === false) $index = count($inputArr);
$outputArr[$i++] = array_slice($inputArr,0,$index);
$inputArr = array_slice($inputArr,$index+1);
}
return $outputArr;
}
If and only if you are using a string for the split string which is not ever going to show up inside the middle of the other strings (space may or may not be the case for this) then I agree with the others that implode and explode are much simpler. In your case:
/**
* #param array $inputArr The array you wish to split.
* #param string $splitStr The string you wish to split by.
*
* #return array
*/
function split_arrays3($inputArr,$splitStr) {
$outputArr = array(); $i=0;
$str = implode("|",$inputArr);
$arr = explode("|$splitStr|");
foreach($arr as $i=>$string)
$outputArr[$i] = explode("|",$string);
return $outputArr;
}
$res = array();
foreach(explode("\n\n", implode("\n", $arr)) as $k => $v) {
$res[$k] = explode("\n", $v);
}
$res will contains the array you want.
Do you have two characters that will not occur in the array string for sure?
If so, suppose they are # and ;
We'll use combination of implode and explode
Then:
$arrstring = implode(';',$old_array); // Split via ; spearators
$arrstring = str_replace(';;','#',$arrstring); // If two ;; then mark end of array as #
$new_pre_array = explode('#',$arrstring); // Outer array
// Inner array
foreach($new_pre_array as $key => $entry) {
$temp_sub_array = explode(';',$entry);
$new_pre_array[$key] = $temp_sub_array;
}
return $new_pre_array;
Reference:
http://php.net/manual/en/function.explode.php
http://php.net/manual/en/function.implode.php
$chunked = chunkArray($your_array);
function chunkArray($array){
$return = array();
$index = 0;
foreach($array as $ar){
if($ar == "" or $ar == null){
$index++;
}
else{
$return[$index][] = $ar;
}
}
return $return;
}
I was opting to first locate at which places the array $array has to be splitted:
$splitAt = array_keys($array, '', TRUE);
This actually is the most magic. The array_splice function then can be used to get the chunks from the input array, only a temporary variable, I named it $last:
$last = 0;
is used to keep track because the length is not the position. Those $splitAt are then used to be inserted into the
$result = array();
$result array:
foreach($splitAt as $pos) {
$result[] = array_splice($array, 0, $pos - $last);
unset($array[0]);
$last += $pos + 1;
}
The leftover chunk from the array is then added as last element:
$result[] = $array;
And that's it. The full Example (Demo):
/**
* explode array by delimiter
*
* #param mixed $delimiter
* #param array $array
* #return array
*/
function array_explode($delimiter, array $array)
{
$splitAt = array_keys($array, $delimiter, TRUE);
$last = 0;
$result = array();
foreach ($splitAt as $pos) {
$result[] = array_splice($array, 0, $pos - $last);
$array && unset($array[0]);
$last += $pos + 1;
}
$result[] = $array;
return $result;
}
# Usage:
var_dump(array_explode('', $array));
I named the function array_explode because it works like explode on strings but for arrays.
The second option I see (and which might be preferrable) is to loop over the array and process it according to it's value: Either add to the current element or add the current to it:
/**
* explode array by delimiter
*
* #param mixed $delimiter
* #param array $array
* #return array
*/
function array_explode($delimiter, array $array)
{
$result[] = &$current;
foreach($array as $value) {
if ($value === $delimiter) {
unset($current);
$result[] = &$current;
continue;
}
$current[] = $value;
}
return $result;
}
I'm trying to generate an array of random numbers from 0-n then shuffle (but ensure that the keys and values DO NOT match).
For example:
0 => 3
1 => 2
2 => 4
3 => 0
4 => 1
Note that both keys and values are from 0-4 but none of the keys and values are the same.
Any thoughts?
A even shorter solution:
$random_number_array = range(0, 100);
shuffle($random_number_array );
$random_number_array = array_slice($random_number_array ,0,10);
print_r($random_number_array);
Result will be:
[0] => 53
[1] => 6
[2] => 16
[3] => 59
[4] => 8
[5] => 18
[6] => 62
[7] => 39
[8] => 22
[9] => 26
$max = 5;
$done = false;
while(!$done){
$numbers = range(0, $max);
shuffle($numbers);
$done = true;
foreach($numbers as $key => $val){
if($key == $val){
$done = false;
break;
}
}
}
This will generate an array with 10 random numbers from 0 to 100:
array_map(function () {
return rand(0, 100);
}, array_fill(0, 10, null));
Result:
array(10) {
[0]=>
int(15)
[1]=>
int(97)
[2]=>
int(20)
[3]=>
int(64)
[4]=>
int(57)
[5]=>
int(38)
[6]=>
int(16)
[7]=>
int(53)
[8]=>
int(56)
[9]=>
int(22)
}
Explanation:
array_fill(0, 10, null) will generate an array with 10 empty items
array_map Applies the callback (first argument) to each item of the array it receives (second argument). In this example, we just return a random number for each array item.
Playground: https://3v4l.org/FffN6
If you need to make sure each generated number is unique:
$uniqueNumbers = 100;
$picked = [];
$uniqueRandomNumbers = array_map(function () use(&$picked, $uniqueNumbers) {
do {
$rand = rand(0, $uniqueNumbers);
} while(in_array($rand, $picked));
$picked[] = $rand;
return $rand;
}, array_fill(0, $uniqueNumbers, null));
https://3v4l.org/mSWBo#v8.0.9
Naive solution:
$n = 10;
$rands = array();
for($i=0; $i<$n;$i++) {
$ok = false;
while(!$ok) {
$x=mt_rand(0,$n-1);
$ok = !in_array($x, $rands) && $x != $i;
}
$rands[$i]=$x;
}
var_dump($rands);
Efficient solution:
$n = 100;
$numbers = range(0, $n-1);
$rands = array();
for ($i=0; $i < $n; $i++) {
$ok = false;
while (!$ok) {
$x = array_rand($numbers);
$ok = !in_array($numbers[$x], $rands) && $numbers[$x] != $i;
}
$rands[$i] = $numbers[$x];
unset($numbers[$x]);
}
var_dump($rands);
edit: s/rand/mt_rand/
edit #2: both solutions can end up in a deadlock, as mentioned by #AMayer. I stand corrected.
Here's a rather long, but also pretty efficient solution, I believe. Contrary to other solutions posted here, this cannot deadlock (unless $size<2), and this will not do a full shuffle every time one value doesn't fit. Instead, it will only replace that value with another, random value.
function unique_list($size=5) {
function all_unique($numbers) {
foreach ($numbers as $key=>$value)
if ($key==$value) return false;
return true;
}
function flip($a, $b, &$numbers) {
$numbers[$a] = $numbers[$a] + $numbers[$b];
$numbers[$b] = $numbers[$a] - $numbers[$b];
$numbers[$a] = $numbers[$a] - $numbers[$b];
}
$flip_count = 0;
$numbers = range(0,$size-1);
shuffle($numbers);
while (!all_unique($numbers)) {
foreach ($numbers as $key=>$value) {
if ($key==$value) {
flip($key, rand(0,$size-1), $numbers);
$flip_count++;
break;
}
}
}
printf("Flipped %d values\n", $flip_count);
return $numbers;
}
$list = unique_list(10);
print_r($list);
The above will print something similar to
Flipped 1 value(s)
Array
(
[0] => 2
[1] => 5
[2] => 7
[3] => 9
[4] => 6
[5] => 3
[6] => 1
[7] => 8
[8] => 0
[9] => 4
)
What's about combining range(...) (for generating the original array first), shuffle(...) (to randomize the array), and array_intersect_assoc(...) (for checking the result array)?
$numbers = range(0, 4);
do {
shuffle($numbers); // print_r($numbers);
$matchingKeyValuePairs = array_intersect_assoc(array_keys($numbers), array_values($numbers)); // print_r($matchingKeyValuePairs);
} while (! empty($matchingKeyValuePairs));
This solution might bring some performance issues for big numbers of elements. But it can be extended by a logic for dealing with the $matchingKeyValuePairs. So while now the logic is like "IF there are matchingKeyValuePairs THEN try it again", a much more efficient logic migth be "IF there are matchingKeyValuePairs THEN cut the matchingKeyValuePairs from the array, randomize this sub-array (multiple times, if needed), and merge it back".
The approach here is to create a range of numbers from 0 to n, and then shuffle.
We then look for key value matches, and swap pairs of them. If we don't have a pair to swap, we swap with the last item, unless the item is the last item, then we swap with the first.
<?php
$n = 5;
$values = range(0, $n);
shuffle($values);
$matched = array_filter($values, function($v, $k) {return $k === $v;}, ARRAY_FILTER_USE_BOTH);
foreach(array_chunk($matched, 2) as list($a, $b)) {
if($b === null) {
$swap_key = array_key_last($values);
if($swap_key == $a) {
$swap_key = array_key_first($values);
}
list($values[$a], $values[$swap_key]) = [$values[$swap_key], $a];
} else {
list($values[$a], $values[$b]) = [$b, $a];
}
}
Example shuffle when n is 5:
array (
0 => 0,
1 => 5,
2 => 1,
3 => 3,
4 => 4,
5 => 2,
)
Here we have matches for keys:
0, 3 and 4.
So we swap the values for keys 0 and 3, and 4 with the last.
array (
0 => 3,
1 => 5,
2 => 1,
3 => 0,
4 => 2,
5 => 4,
)
(array_key_first could be swapped for 0 given the range here. I've left it as it is more explicit.)
public function getUniqueArray($value, $total)
{
$array = [];
for ($i = 0; $i < $value; $i++) {
$rand = rand(0, $total);
if (in_array($rand, $array))
$i--;
else
array_push($array, rand(0, $total));
}
return $array;
}