How to select randomly from array exluding one value? - php

With the holidays slowly nearing, It's time to pick straws again. We always pick a piece of paper from a box containing everbody's name. However, this year I wanted to solve the issue of picking your own name from the box by using PHP.
I've got an array with names ex:
$names = [
'John',
'Jane',
'Joe',
'Matt',
'Steve',
'Anne',
'Karin'
];
For each of these names, I wanna pick 4 random names, so that at the end every person has 4 names for which they will have to buy a present.
The issue
Well, I'm stuck. I've thought of a million ways on how to do this but I just can't come up with anything.
What I've tried so far
Using a foreach
Using array_rand()
Shifting the array
The problem with these is that a person shouldn't be picking their own name and that everyone should be picked an even amount of times (4).
Update
Some great answers already posted. I'm not exactly sure though if this is fair for everyone. Everyone should at the end get 4 presents from 4 different persons.
I was thinking of adding each name to the $names array 4 times and then applying one of your answers. Am I on the right track with this?
Update 2
What I've tried now:
function select_rand($array, $exclude_array) {
$diff = array_diff($array, $exclude_array);
return $diff[array_rand($diff, 1)];
}
$workArray = [
'0' => $names,
'1' => $names,
'2' => $names,
'3' => $names,
];
foreach ($names as $k => $name) {
$i = 0;
$new[$name] = array();
while ($i < 4) {
$value = select_rand($workArray[$i], array_merge($new[$name], array($name)));
if (($key = array_search($value, $workArray[$i])) !== false) {
unset($workArray[$i][$key]);
}
$new[$name][] = $value;
$i++;
}
}
This works only in a few caes.

I would use shuffle, and array_slice for this job:
$names = [
'John',
'Jane',
'Joe',
'Matt',
'Steve',
'Anne',
'Karin'
];
foreach ($names as $name) {
// working array
$working = $names;
// remove current name from array,no one wants to buy presents for him/her self..
unset($working[$name]);
shuffle($working);
$people = array_slice($working, 0, 4);
echo $name . ' has to buy presents for:';
var_dump($people);
}

You can create custom function like as
$names = [
'John',
'Jane',
'Joe',
'Matt',
'Steve',
'Anne',
'Karin'
];
$my_name = "John";
$limit = 4;
function get_name($arr,$your_name,$limit){
$key = array_flip($arr)[$your_name];
unset($arr[$key]);
$rand_keys = array_rand($arr,$limit);
$result = array_intersect_key($arr, array_flip($rand_keys));
return implode(',',$result);
}
echo get_name($names,$my_name,$limit);
Demo

This can help -
function select_rand($array, $exclude_array) {
$diff= array_diff($array, $exclude_array);
return $diff[array_rand($diff, 1)];
}
$names = array(
'John',
'Jane',
'Joe',
'Matt',
'Steve',
'Anne',
'Karin'
);
foreach($names as $name)
{
$i = 0;
$new[$name] = array();
while($i < 4) {
$new[$name][] = select_rand($names, array_merge($new[$name], array($name)));
$i++;
}
}
This will generate a new array for each name (as key) in that array containing 4 unique names.
FIDDLE
Update
$new[$name][] = select_rand($names, array_merge($new[$name], array($name, 'Karin')));

I think if you want to have an even distribution of presents per person, you need to have more control over it. Anyhow, in this code I keep track of number of presents per person. I initialise it with zero: $presents = array_fill_keys($names, 0);. Then after each selection I updated this number $presents[$key]++;.
<?php
$names = array(
'John',
'Jane',
'Joe',
'Matt',
'Steve',
'Anne',
'Karin'
);
$presents_number = 4;
// initialization
$presents = array_fill_keys($names, 0);
$names_names = array();
foreach ($names as $i => $picker) {
$box = $names;
unset($box[$i]);
// filter out the people with maximum presents:
foreach ($presents as $key => $number) {
if (($presents[$key] > $presents_number-1) && in_array($key, $box)) {
$box = array_diff($box, array($key));
}
}
// shuffle the box and select 4 top
shuffle($box);
$selection = array_slice($box, 0, $presents_number);
foreach ($selection as $key) {
$presents[$key]++;
}
$names_names[$picker] = $selection;
}
echo "<pre>";
print_r($names_names);
echo "</pre>";
There can be more to be considered mathematically. Specially since loops can happen, this algorithm can go wrong. I haven't spend time on it. But as an example of 3 people and one present per person. (A, B, C)
correct answer:
A => {B}
B => {C}
C => {A}
wrong answer:
A => {B}
B => {A}
C => {}
Basically, the perfect algorithm should to avoid these wrong answers. Maybe later I fixed the problem as an update for this post.

Related

Find element inside a PHP array, with grouping of numbers

I have a long multi-dimensional array, with groups of numbers counting up to 100 ... For example:
$list = (
array('1-4','first'),
array('5-7','next'),
array('8-10','third group'),
array('11-12','another group'),
/* ... keep going up to 100 */
array('94-97','almost done'),
array('98-100','first')
);
I have a randomly generated number, to figure out with line to output:
$num = rand(1,100);
I'd like to know how I would go about getting the # associated inside the array. So, if I rolled a 3, I could get the first array key (1-4), and be able to print out "first" .... or if I rolled a 97, I would get the 2nd-to-last key for "almost done" (94-97). Is there a quick way to do this?
Thanks
You could do something like this if the structure of $list is guaranteed:
$randomRoll = rand(1,100);
array_walk($list, function($ar) use ($randomRoll){
$nums = explode('-', $ar[0]);
(int) $lower = $nums[0];
(int) $upper = $nums[1];
if (($randomRoll >= $lower) && ($randomRoll <= $upper))
{
print($ar[1]);
}
});
You can simplify your lookup array into an associative one since there are no gaps from 1 to 100. In other words, every random number will fall into one of the known groups by determining the greatest number that it is less than or equal to.
Don't use any functional iterators (e.g. array_filter() or array_walk()) because they will unconditionally iterate the entire lookup array. Instead, use foreach() and break as soon as possible -- this is most efficient.
Code: (Demo)
$list = [
4 => 'first', // 1 - 4
7 => 'next', // 5 = 7
10 => 'third group', // 8 - 10
12 => 'another group', // 11, 12
95 => 'huge chunk', // 13 - 95
97 => 'almost done', // 96, 97
100 => 'first' // 98 - 100
];
$num = rand(1, 100);
foreach ($list as $key => $group) {
// echo "$num vs $key\n";
if ($num <= $key) {
break; // don't iterate anymore after finding group
}
}
echo "Num = $num, Group = $group";
I did the following... and it seems to work. If anyone has advice on cleaning up the code, let me know... Thanks...
$dieroll = rand(1,100); // Final Roll
$numarr = array(); $j=0;
foreach ($list as $k => $v) {
$strTr = $v[0];
$nums2arr = explode("-", $strTr);
for($i=$nums2arr[0]; $i<=$nums2arr[1]; $i++) {
$numarr[] = $j;
}
$j++;
}
$roll = $list[$numarr[$dieroll-1]][1];
This solution uses array_filter. The result is again a 2-dimensional array with the elements that fulfill the condition of the filter function.
$filter = function($a) use($find){
list($low,$up) = explode('-',$a[0]);
return $find >= $low AND $find <= $up;
};
$arr = array_filter($list, $filter);
full example:
$list = [
['1-4','first'],
['5-7','next'],
['8-10','third group'],
['11-12','another group'],
/* ... keep going up to 100 */
['94-97','almost done'],
['98-100','first']
];
//for a test is better to use a fix number
$find = 9;
$filter = function($a) use($find){
list($low,$up) = explode('-',$a[0]);
return $find >= $low AND $find <= $up;
};
$arr = array_filter($list, $filter);
echo 'Label: '.reset($arr)[1]."<br>\n";
echo 'Area: '.reset($arr)[0]."<br>\n";
echo 'Array-Key: '.key($arr)."<br>\n";
Output:
Label: third group
Area: 8-10
Array-Key: 2
As of PHP 8.0, solutions with match are also possible for this:
$num = rand(1, 100);
$group = match(true) {
$num <= 4 => 'first',
$num <= 7 => 'next',
$num <= 10 => 'third group',
$num <= 12 => 'another group',
$num <= 95 => 'huge chunk',
$num <= 97 => 'amost done',
$num <= 100 => 'first',
};
echo "Num = $num, Group = $group";

Cluster PHP array

Let's say I have an array of items with each item a value. I'd like to
create a new array where the items are clustered by their relative distance to each other.
When an item has a distance of one to another item, they belong to each other.
$input = [
'item-a' => 1,
'item-b' => 2,
'item-c' => 3,
'item-d' => 5,
];
$output = [
['item-a', 'item-b'],
['item-b', 'item-c'],
['item-d'],
];
This will create an output of overlapping arrays. What I want is that, because item-a and item-b are related, and item-b is also
related to item-c, I'd like to group item-a, item-b, and item-c to each other. The distance to item-c and item-d is greater than
1 so it will for a cluster of itself.
$output = [
['item-a', 'item-b', 'item-c'],
['item-d'],
];
How do I even start coding this?
Thanks in advance and have a nice day!
This can only be tested in your environment but here is what it does
it attempts to find relative distances based on array index 0's hash
it resorts the input array by distances (assuming that in this stage some will be positive and some negative) - that gives us the info to put the hash array in an order
Take this new array and put the hash back in
build a final output array measuring distances and sorting the level of output array by a threshhold.
I put in a couple dummy functions to return distances, obviously replace with your own. This might need tweaking but at this point, it's in your hands.
<?php
// example code
$input = [
'item-a' => 'a234234d',
'item-f' => 'h234234e',
'item-h' => 'e234234f',
'item-b' => 'f234234g',
'item-m' => 'd234234j',
'item-d' => 'm234234s',
'item-e' => 'n234234d',
'item-r' => 's234234g',
'item-g' => 'f234234f',
];
function getDistanceFrom($from, $to) {
return rand(-3,3);
}
function getDistanceFrom2($from, $to) {
return rand(0,7);
}
// first sort by relative distance from the first one
$tmp = [];
$ctr = 0;
foreach ($input as $item => $hash) {
if ($ctr === 0) { $ctr ++; continue; }
$tmp[$item]=getDistanceFrom(reset($input), $hash);
}
uasort($tmp, function ($a, $b)
{
return ($a < $b) ? -1 : 1;
});
//now they're in order, ditch the relative distance and put the hash back in
$sortedinput = [];
foreach ($tmp as $item => $d) {
$sortedinput[$item] = $input[$item];
}
$output=[];
$last=0;
$level=0;
$thresh = 3; // if item is within 3 of the previous, group
foreach($sortedinput as $v=>$i) {
$distance = getDistanceFrom2($last, $i);
if (abs($distance) > $thresh) $level++;
$output[$level][]=array("item" => $v, "distance" => $distance, "hash" => $i);
$last = $i;
}
print_r($output);

How to return value of an array inside a loop and use it outside in PHP

I have a code like this:
Lets assume that this arrays has this values:
$arr1 = array();
$arr2 = array();
$result = array();
$arr1[] = array( 'grade' => [1,2,3,4] );
$arr2[] = array( 'grade' => [1,2,3,4] );
foreach($arr1 as $a1){
$set1 = $a1['grade'];
foreach($arr2 as $a2){
$set2 = $a2['grade'];
}
$result[] = array('show_result' => $set1+$set2);
}
foreach{$result as $res){
echo $res['show_result'];
}
The output of the array $res['show_result'] must be:
2, 4, 6, 8
But I get the wrong addition of this arrays. Help will be much appreciated.
As Joni said, your first error is on line 3: ' should be ;
Then, you're not filling arrays like you wanted : array( 'grade' => 1,2,3,4 ); creates an array with first key is 'grade' with value '1', then second key is '0' with value '2' etc...
Your last foreach loop has a syntax error similar to your first error.
See a working correction here
$arr1 = array();
$arr2 = array();
$result = array();
array_push($arr1, 1, 2, 3, 4); //fill array with 4 values (integers)
array_push($arr2, 1, 2, 3, 4); //fill array with 4 values (integers)
//so $arr1 & $arr2 are now a 4 elements arrays
$length = count($arr1); //size of array, here 4
for ($i = 0; $i < $length; $i++) { //loop over arrays
array_push($result, ($arr1[$i] + $arr2[$i])); //fill the results array with sum of the values from the same position
}
var_dump($result);
You have quite a few syntax errors in your code.
Although this solution works, the idea behind using the same counter, $i, to extract a value from both arrays is brittle. For example, you'll get an Undefined offset if the first array has 5 grades instead of 4. If you take a step back and explain your problem in the larger context, perhaps we can provide a better solution. I get the sneaking suspicion you're asking an XY Problem.
http://sandbox.onlinephpfunctions.com/code/bb4f492c183fcde1cf4edd50de7ceebf19fe343a
<?php
$gradeList1 = ['grade' => [1,2,3,4]];
$gradeList2 = ['grade' => [1,2,3,4]];
$result = [];
for ($i = 0; $i < count($gradeList1['grade']); $i++) {
$first = $gradeList1['grade'][$i];
$second = $gradeList2['grade'][$i];
$result['show_result'][] = (int)$first + (int)$second;
}
var_dump($result);

Position of Max value in Array PHP

Hey guys,
so I have two arrays
$names = array('jimmy', 'johnny', 'sarah');
$ages= array('16', '18', '12');
I am trying to find the biggest/maximum value in ages, get that elements position and use said position to get the corresponding name. How would I achieve this?
Is there a better way to achieve the corresponding name, perhaps bundling all in one?
Thanks
This should get the key for you:
$names = array('jimmy', 'johnny', 'sarah');
$ages= array('16', '18', '12');
$oldest = array_search(max($ages), $ages);
echo $names[$oldest];
You should note though, that if two persons would have the same age, the first of these two persons would be the one returned.
if you need to find all the oldest you should use array_keys() instead of array_search() like this:
$names = array('jimmy', 'johnny', 'sarah', 'kristine');
$ages = array('16', '18', '12', '18');
$oldestPersons = array_keys($ages, max($ages));
foreach($oldestPersons as $key) {
echo $names[$key].'<br />';
}
$oldest_key = 0;
$age_var = 0;
foreach ($ages as $key => $age) {
if ($age > $age_var) {
$age_var = $age;
$oldest_key = $key;
}
}
echo "Oldest person is: {$names[$oldest_key]} ($age_var)";
Just for fun, here's another solution:
$names = array('jimmy', 'johnny', 'sarah');
$ages= array('16', '18', '12');
$people = array_combine($ages, $names);
ksort($people);
echo 'Oldest person is: '.end($people);
See it in action.
Note: If many people have the same age, the one appearing last in the input arrays gets picked. This is a result of the behavior of array_combine.

elegant way to map two arrays to each other

I've got two arrays, one with IDs and one with Names:
$ids = array(4, 13, 6, 8, 10);
$names = array('alice', 'bob', 'charles', 'david', 'elizabeth');
I need to update the db so that the rows with the ids have the names in the array. Here's the tricky bit: I also have two ints:
$special_name = 2; // the index in $names, in this case we mean 'charles'
$special_id = 13; // the id value
I don't care about which name goes to which id, except that the name with the $special_name should go on the $special_id.
What's the most elegant way to get there? All of the methods that I'm thinking of seem pretty messy. The best I've thought of is to extract out the special items from each array, and do those first, and then do the rest, perhaps building a new array like this:
$mapped = new array();
$mapped[$special_id] = $names[$special_name];
foreach ($ids as $id) {
if ($id != $special_id) {
$mapped[$id] = current($names);
}
// advance $names pointer
$next_name = next($names);
if ($next_name == $special_name) next($names);
}
I haven't tested that yet (I'm about to) but it's meant to produce something like:
$mapped = array(13=>'charles', 4=>'alice',6=>'bob', 8=>'david', 10=>'elizabeth');
and then running through that to do the actual update. Got a better idea?
UPDATE: added the possible solution above. Meanwhile a couple answers have come in.
If it wasn't for the special Ids, you could have just array_combine'd the two arrays. Here is how I think to have solved the issue:
Setup
$ids = array(4, 13, 6, 8, 10);
$names = array('alice', 'bob', 'charles', 'david', 'elizabeth');
$specialNameIndex = 2;
$specialId = 13;
Solution
$result = array($specialId => $names[$specialNameIndex]);
unset($ids[array_search($specialId, $ids)],
$names[$specialNameIndex]);
$result += array_combine($ids, $names);
Result
print_r($result);
Array
(
[13] => charles
[4] => alice
[6] => bob
[8] => david
[10] => elizabeth
)
you can use array_combine and then set/append your special values:
$mapped = array_combine($ids, $names);
$mapped[$special_id] = $names[$special_name];
Are the $ids and $names arrays synced? (Does 4 correspond to 'alice'?)
for ($i=0; $i < count($ids); $i++) {
$indexed[$ids[$i]] = $names[$i]; // $indexed[4] = 'alice';
$indexed2[] = array ( $ids[$i] => $names[$i] ); // $indexed[0] = ( 4 => 'alice')
}
Pick your fave
Since you use the default indexes you can use foreach() on keys($ids) to get the indexes so that you can iterate through both arrays at once. Just compare the value of the current index of $ids and use the alternate index of $names when appropriate.

Categories