Generate array of random unique numbers in PHP - php

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;
}

Related

How To Reduce and Average Total PHP Array Elements To New Size

Whats the best solution to take an array with 100 elements (all numbers) and reduce the array size to a smaller number of elements averaging the in between/combined numbers into new. I do NOT mean slice or crop.
Example:
$array = [10,20,30,40,50,60];
$final_count = 3;
$new_array = array_slimmer($array, $final_count);
Output:
[15,35,55]
Here's a solution based on an existing answer about breaking an array into a set number of chunks:
$array = [11, 3, 45, 6, 61, 89, 22];
function array_slimmer(array $array, int $finalCount): array
{
// no work to be done if we're asking for equal or more than what the array holds
// same goes if we're asking for just one array or (nonsensical) less
if ($finalCount >= count($array) || $finalCount < 2) {
return $array;
}
return array_map(function (array $chunk) {
// rounded to two decimals, but you can modify to accommodate your needs
return round(array_sum($chunk) / count($chunk), 2);
}, custom_chunk($array, $finalCount));
}
// this function is from the linked answer
function custom_chunk($array, $maxrows) {
$size = sizeof($array);
$columns = ceil($size / $maxrows);
$fullrows = $size - ($columns - 1) * $maxrows;
for ($i = 0; $i < $maxrows; ++$i) {
$result[] = array_splice($array, 0, ($i < $fullrows ? $columns : $columns - 1));
}
return $result;
}
print_r(array_slimmer($array, 2));
print_r(array_slimmer($array, 3));
print_r(array_slimmer($array, 4));
This outputs:
Array ( [0] => 16.25 [1] => 57.33 )
Array ( [0] => 19.67 [1] => 33.5 [2] => 55.5 )
Array ( [0] => 7 [1] => 25.5 [2] => 75 [3] => 22 )
Demo
You could use array_chunk to split your array into pieces, the size being count($array) / $final_count, then use array_map to take the average value from each of those chunks (array_sum($chunk) / count($chunk)):
$array = [10,20,30,40,50,60];
$final_count = 3;
$new_array = array_map(function ($a) {
return array_sum($a) / count($a);
}, array_chunk($array, (int)(count($array) / $final_count)));
print_r($new_array);
Output
Array
(
[0] => 15
[1] => 35
[2] => 55
)
Demo on 3v4l.org
Note
This will give extra values on the end if the array length is not evenly divisible by $final_count (demo). You will need to pad the array out in that case to a multiple of that length using code such as this, which replicates the last value in the array:
while (count($array) % $final_count != 0) {
$array[] = end($array);
}
Demo on 3v4l.org

Sorting an array with bubble sort [duplicate]

This question already has answers here:
How can I sort arrays and data in PHP?
(14 answers)
Closed 3 years ago.
I am trying to create an algorithm that shows each step of bubble sort, sorting one number at a time. I was was able to sort the number at the first index, but I need to figure out how to sort all the numbers.
$x = array (9,7,5,3,0);
$count = count($x);
for($i = 0; $i < $count-1; $i++ ) {
$temp = $x[$i+1];
$x[$i+1] = $x[$i];
$x[$i] = $temp;
echo '<pre>';
print_r($x);
}
My current output is:
Array
(
[0] => 7
[1] => 9
[2] => 5
[3] => 3
[4] => 0
)
Array
(
[0] => 7
[1] => 5
[2] => 9
[3] => 3
[4] => 0
)
Array
(
[0] => 7
[1] => 5
[2] => 3
[3] => 9
[4] => 0
)
Array
(
[0] => 7
[1] => 5
[2] => 3
[3] => 0
[4] => 9
)
From here I need to continue sorting the remaining numbers. For 7, the output should be
57390
53790
53970
53907
and then for 3
35079
30579
30759
30795
and then for 0, it should be same for all 4 lines like
03570
03579
03579
03579
[The assigned problem.][1]
Two issues:
You should only swap values when they are not in the right order
You need an outer loop to repeat this inner loop, similar to a proper bubble sort algorithm.
Your code can be modified like below so it generates the required output:
$x = array (9,7,5,3,0);
$count = count($x) - 1;
for($times = 0; $times < $count; $times++) {
for($i = 0; $i < $count; $i++ ) {
$temp = $x[$i+1];
if ($temp < $x[$i]) {
$x[$i+1] = $x[$i];
$x[$i] = $temp;
}
echo implode(" ", $x) . "\n";
}
echo "\n";
}
Note that a proper bubble sort algorithm will perform fewer iterations.
Bubble sort is basically simplified into a min() + shift (or sometimes swap) operation that occurs N times. So it's an O(n^2) algorithm.
Since this is for learning purposes I'm going to try to be as verbose as possible.
function getMin(Array &$array): Int {
$min = reset($array);
$minKey = null;
// Find the minimum value
foreach($array as $k => $n) {
if ($n < $min) {
$min = $n;
$minKey = $k;
}
}
// remove the minimum value from the array
$array[$k] = null;
$array = array_filter($array, function ($v) { return $v !== null; });
return $min;
}
$array = [9,7,5,3,0];
foreach ($array as $n => $value) {
// Find the min value in the array from Nth index onward
$min = getMin($array); // get the smallest value in the array and remove it.
$sorted[] = $min; // push it on to the new array
}
var_dump($sorted);
This gives you the expect result:
array(5) {
[0]=>
int(0)
[1]=>
int(3)
[2]=>
int(5)
[3]=>
int(7)
[4]=>
int(9)
}
Of course, this is not the way you would want to implement Bubble Sort, because it's a bit circuitous. Again, it's for educational purposes. You can inspect the $array as it's being modified at each step and the new $sorted as it's being built. Typically you would just swap the min/max values in place and use the same array (keeping track of the keys to rescan the array).
Like this...
// Unsorted $array
$array = [9,7,5,3,0];
// Sort the array
for ($lastKey = $i = 0, $len = count($array); $i < $len; $i++) {
// Scan for minimum value
for ($minKey = $j = $lastKey, $min = $array[$minKey]; $j < $len; $j++) {
if ($array[$j] < $min) {
$minKey = $j;
$min = $array[$j];
}
}
// Swap the values
$swap = $array[$lastKey];
$array[$lastKey] = $min;
$array[$minKey] = $swap;
// Update the scan position
$lastKey++;
}
var_dump($array); // Gives you [0,3,5,7,9]
You are performing unconditional position swapping, but you actually need to check if movement is required.
There is no shortage of tutorials that demonstrate a bubble sort in php. A quick good lead me to this one: https://www.w3resource.com/php-exercises/searching-and-sorting-algorithm/searching-and-sorting-algorithm-exercise-6.php which can be modified for your purposes.
PHP now offers "array destructuring" which means you no longer need to use a temporary holding variable while swapping.
Code: (Demo)
$x = [9,7,5,3,0];
$count = count($x) - 1;
for ($pass = 0; $pass < $count; ++$pass) {
for ($i = 0; $i < $count; ++$i) {
if ($x[$i] > $x[$i + 1]) {
[$x[$i + 1], $x[$i]] = [$x[$i], $x[$i + 1]];
}
$results[] = implode($x);
}
$results[] = "\n";
}
echo implode("\n", $results);

array matching count one less than expected

$ar is a sock of pairs. $n is the number of items in $ar. I have to match each number in the array with another if any of them match, its a pair. I have to then return the count of matched items. I have done it below but the answer is one less than what it should be. Example
n:9
ar: 10 20 20 10 10 30 50 10 20
I get output 2 instead of 3.
function sockMerchant($n, $ar) {
$pair =0;
$j=0;
for($i=0; $i< count($ar); $i++)
{
for($j=$i+1; $j< count($ar); $j++)
{
if ( isset( $ar[$j]) && isset( $ar[$i])) {
if ($ar[$i]== $ar[$j])
{
unset($ar[$i]);
unset($ar[$j]);
$pair+=1;
$i=0;
break;
}
}
}
}
return count($ar);
}
Instead of what you are doing there is shortcut to achieve the same,
$temp = array_count_values($arr); // count number of occurences
echo count(array_filter($temp, function($value){ // filter in not greater than 1
return $value > 1;
}));
The above snippet will give you all the pairs which are not only once.
Here is an one more alternative for your snippet,
$temp = array_count_values($arr); // count number of occurences
$e = array_reduce($temp, function ($carry, $item) {
$carry += ($item > 1 ? intval($item / 2) : 0);
return $carry;
});
echo $e;die;
Working demo.
$ar = [10, 20, 20, 10, 10, 30, 50, 10, 20];
$pairIndex = [];
$count = 0;
foreach ($ar as $key => $item) {
// Start comparing from the next element
for ($i = ($key + 1); $i < count($ar); $i++) {
if ($item == $ar[$i] && !in_array($key, $pairIndex)) {
$pairIndex[] = $key;
$pairIndex[] = $i;
$count++;
}
}
}
echo "Pairs: " . $count;
This will give you the number of pairs.
$arr = array(10, 20, 20, 10, 10, 30, 50, 10, 20);
$counts = array_count_values($arr); // count number of occurences
array_walk($counts, function(&$x) {$x = intdiv($x, 2);}); //divide each element
//by 2 (integer division), so now you have pairs of each element
echo array_sum($counts); //sum the pairs
Using your initial array, it echoes 3.
simple example what happens to your array:
$ar = array(0, 10, 20, 30, 40, 50 , 60, 70, 80, 90);
$lnPointer = 2;
print_r($ar);
// Array ( [0] => 0 [1] => 10 [2] => 20 [3] => 30 [4] => 40 [5] => 50 [6] => 60 [7] => 70 [8] => 80 [9] => 90 )
echo "<HR>";
echo $ar[$lnPointer];
echo "<HR>";
unset( $ar[$lnPointer]);
print_r($ar);
// Array ( [0] => 0 [1] => 10 [3] => 30 [4] => 40 [5] => 50 [6] => 60 [7] => 70 [8] => 80 [9] => 90 )
echo "<HR>";
echo $ar[$lnPointer];
after unsetting you get an undefined index
If you want total matched pair count then you should return $pair. But you are returning count($ar) which can not be correct in all the possible scenarios. Check the below code with output and new set of data:
<?php
function sockMerchant($n, $ar) {
$pair =0;
$j=0;
for($i=0; $i< $n; $i++)
{
for($j=$i+1; $j< $n; $j++)
{
if ( isset( $ar[$j]) && isset( $ar[$i])) {
if ($ar[$i]== $ar[$j])
{
unset($ar[$j]);
unset($ar[$i]);
$pair+=1;
$i=0;
break;
}
}
}
}
echo '<pre>'; print_r($ar);
return $pair.'--'.count($ar);
}
$a = [10, 20, 20, 10, 10, 30, 50, 10, 20, 80, 40];
$n = 11;
$b = sockMerchant($n, $a);
var_dump($b);
// Output:
/*
Array
(
[5] => 30
[6] => 50
[8] => 20
[9] => 80
[10] => 40
)
string(4) "3--5"
*/
?>
Demo

How to get the factorial value of each number in an array?

I am trying to get an factorial value of each item in array by using this method but this outputs only one value
can any body help me finding where i am doing wrong?
function mathh($arr, $fn){
for($i = 1; $i < sizeof($arr); $i++){
$arr2 = [];
$arr2[$i] = $fn($arr[$i]);
}
return $arr2;
}
$userDefined = function($value){
$x = 1;
return $x = $value * $x;
};
$arr = [1,2,3,4,5];
$newArray = mathh($arr, $userDefined);
print_r($newArray);
You're going to need a little recursion so in order to do that you need to pass the lambda function into itself by reference:
function mathh($arr, $fn){
$arr2 = []; // moved the array formation out of the for loop so it doesn't get overwritten
for($i = 0; $i < sizeof($arr); $i++){ // starting $i at 0
$arr2[$i] = $fn($arr[$i]);
}
return $arr2;
}
$userDefined = function($value) use (&$userDefined){ // note the reference to the lambda function $userDefined
if(1 == $value) {
return 1;
} else {
return $value * $userDefined($value - 1); // here is the recursion which performs the factorial math
}
};
$arr = [1,2,3,4,5];
$newArray = mathh($arr, $userDefined);
print_r($newArray);
The output:
Array
(
[0] => 1
[1] => 2
[2] => 6
[3] => 24
[4] => 120
)
I wanted to expand on this some since you're essentially (in this case) creating an array map. This could be handy if you're doing additional calculations in your function mathh() but if all you want to do is use the lambda function to create a new array with a range you could do this (utilizing the same lambda we've already created):
$mapped_to_lambda = array_map($userDefined, range(1, 5));
print_r($mapped_to_lambda);
You will get the same output, because the range (1,5) of the mapped array is the same as your original array:
Array
(
[0] => 1
[1] => 2
[2] => 6
[3] => 24
[4] => 120
)

Sorting only odd elements of an associative array

If I have an associative array like
3 => 50
4 => 12
5 => 45
6 => 89
7 => 5
8 => 1
Now I want to sort only the values of odd keys in ascending order.
The output should be:
7 => 5
4 => 12
5 => 45
6 => 89
3 => 50
8 => 1
To maintain original keys, you have to first separate even and odd elements:
$odd = $even = array();
array_walk( $array, function( $val, $key ) use ( &$odd, &$even ) { ( $key % 2 ) ? $odd[$key] = $val : $even[$key] = $val; });
Then, sort $odd array:
asort( $odd );
At the end, you reconstruct the array:
$array = array();
while( current( $odd ) || current( $even ) )
{
if( current( $odd ) ) $array[key($odd)] = current( $odd );
if( current( $even ) ) $array[key($even)] = current( $even );
next( $odd );
next( $even );
}
print_r( $array );
eval.in demo
Note that your question is a bit ambiguous: it's not totally clear if you base odd/even on key value or key position: this solution consider key values and — trough while and if checks — guarantee that all values are preserved, even if you have more even than odd keys (or vice-versa).
Try this:
<?php
$array = array(
3 => 50,
4 => 12,
5 => 45,
6 => 89,
7 => 5,
8 => 1
);
$oddElems = array();
$evenElems = array();
$i = 0;
foreach($array as $index => $value)
{
if($i % 2 == 0)
$oddElems[$index] = $value;
else
$evenElems[$index] = $value;
$i++;
}
//sort odd elements
asort($oddElems);
$result = array();
while(!empty($oddElems) || !empty($evenElems))
{
$lastEvenElemKey = array_keys($evenElems, end($evenElems))[0];
$evenElem = array_pop($evenElems);
if($evenElem !== null)
$result[$lastEvenElemKey] = $evenElem;
$lastOddElemKey = array_keys($oddElems, end($oddElems))[0];
$oddElem = array_pop($oddElems);
if($oddElem !== null)
$result[$lastOddElemKey] = $oddElem;
}
echo '<pre>';
$result = array_reverse($result, true);
print_r($result);
Result is:
Array
(
[7] => 5
[4] => 12
[5] => 45
[6] => 89
[3] => 50
[8] => 1
)
This is how you could do this in PHP, is this what you are looking for ?
$array(3 => 50, 4 => 12, 5 => 45, 6 => 89, 7 => 5, 8 => 1);
function sort_it($array) {
$final_array[];
$array_only_odd_keys[];
$array_only_odd_stuff[];
$array_only_even_keys[];
$array_only_even_stuff[];
foreach($array as $key => $value) {
if($key & 1) {
//Key is odd
array_push($array_only_odd_stuff, $key => $value);
array_push($array_only_odd_keys, $key);
}
else {
// Key is even
array_push($array_only_even_stuff, $key => $value);
array_push($array_only_even_keys, $key);
}
}
$array_only_odd_keys = asort($array_only_odd_keys);
for ($x = 0; $x <= count($array)/2; $x++) {
if ($x & 1) {
array_push($final_array, $array_only_odd_keys[$x], $array_only_odd_stuff[$array_only_odd_keys[$x]]);
}
else {
array_push($final_array, $array_only_even_keys[$x], $array_only_odd_stuff[$array_only_even_keys[$x]]);
}
}
return $final_array;
This is how i'd do it in PHP though only if you want the final array to switch between even and odd keys like in your example.
The easiest way is probably to separate the array into even and odd arrays, sort the odd array, and then glue them back together:
$odd = $even = [];
foreach (array_chunk($myArray, 2, true) as $pair) {
$odd += array_slice($pair, 0, 1, true);
$even += array_slice($pair, 1, 1, true);
}
asort($odd);
$result = [];
for ($i = 0; $i < count($odd); $i++) {
$result += array_slice($odd, $i, 1, true);
$result += array_slice($even, $i, 1, true);
}
This rather naïve implementation assumes there is an equal number of odd and even numbers, they're always alternating and the array starts with an odd number.

Categories