array_multisort to sort several arrays - php

I've searched for this but can't seem to find the exact answer. I want to use array_multisort to simultanously sort 3 arrays based on numeric values in 3 of the arrays. Basically I want to make a "standings" table similar to what you'd see for NFL/NHL standings etc. I have 3 arrays, tempIDs (string), tempWins (numeric), tempWinPercentage (numeric). I need all 3 to be sorted at the same time based first on wins, and then if there is a tie, win percentage.
I can't seem to get array_multisort to work with more than just 2 arrays, so maybe I'm misunderstanding the terminology when they say that it can work with "several" arrays. Thank you!

You should have a data array like this:
$data = array(
0 => array(
'tempIDs' => 'something',
'tempWins' => 10,
'tempWinPercentage' => 50,
),
1 => array(
'tempIDs' => 'something else',
'tempWins' => 10,
'tempWinPercentage' => 60,
),
3 => array(
'tempIDs' => 'something more',
'tempWins' => 20,
'tempWinPercentage' => 50,
),
);
Then sort this array using usort($data, 'my_sort_cb')
Your callback method should first compare tempWins, and if they are equal, compare tempWinPercentages:
function my_sort_cb($a, $b) {
if ($a['tempWins'] > $b['tempWins']) return 1;
if ($a['tempWins'] < $b['tempWins']) return -1;
if ($a['tempWinPercentage'] > $b['tempWinPercentage']) return 1;
if ($a['tempWinPercentage'] < $b['tempWinPercentage']) return -1;
return 0;
}
(this can be made shorter)

I can't seem to get array_multisort to
work with more than just 2 arrays, so
maybe I'm misunderstanding the
terminology when they say that it can
work with "several" arrays. Thank you!
I think they mean it can be used for sorting more than two arrays, but the other arrays will be sorted basing on the first one.
In example, executing this code
$a1 = array(12, 23, 34, 45, 45, 34);
$a2 = array(234, 56, 243, 456, 34, 346);
$a3 = array(654, 56, 8, 12, 56, 90);
array_multisort($a1, $a2, $a3);
you will get the arrays sorted as if the would have been defined as
$a1 = array(12, 23, 34, 34, 45, 45);
$a3 = array(654, 56, 8, 90, 56, 12);

Related

Select multiple random column values from a two-dimensional array

I want to select 5 random ID's from my array of rows. Here is my array $test:
$test = [
['id' => 13, 'pets' => 8],
['id' => 15, 'pets' => 8],
['id' => 16, 'pets' => 10],
['id' => 17, 'pets' => 9],
['id' => 18, 'pets' => 10],
['id' => 19, 'pets' => 10],
['id' => 20, 'pets' => 0],
['id' => 21, 'pets' => 8],
['id' => 22, 'pets' => 9],
['id' => 23, 'pets' => 4],
['id' => 24, 'pets' => 0],
['id' => 40, 'pets' => 8],
['id' => 43, 'pets' => 2],
];
How can I select 5 random ID's from the array and put them into a string like this:
$ids = '13,17,18,21,43';
I've tried to use array_rand(), but it does not seem to work for my type of array. I'm not sure if there are any other built in PHP functions that can do this type of job or if I have to create my own function. It would be nice to have my own function like this to plug in the number of required values.
You can use array_column to only get the ID's and shuffle them.
Then use array_slice to get five items and implode.
$id = array_column($arr, "id");
Shuffle($id);
Echo implode(",", array_slice($id, 0, 5));
First extract the id column indexing also by the id, then pick 5 random ones, and finally implode into a comma separated list. Since keys must be unique, this has the added benefit of not returning duplicate ids if there happen to be duplicates in the array:
$ids = implode(',', array_rand(array_column($test, 'id', 'id'), 5));
For a function:
function array_rand_multi($array, $key, $num) {
return implode(',', array_rand(array_column($array, $key, $key), $num));
}
If you want random, unique ids in a random order, I recommend shuffling the array, then isolating upto 5 subarrays, then extracting the id values, then joining with commas. This way array_column() doesn't need to iterate the full array.
Code: (Demo)
shuffle($test);
echo implode(
',',
array_column(
array_slice($test, -5),
'id'
)
);
If you want random, unique ids and don't mind that they will be in the same order as your input rows, then array_rand() can be used.
#AbraCadaver's approach works by applying temporary keys to the input array, picking five random keys, then joining with commas. Because the values inside the rows are never used, null can also be used as array_column()'s second parameter. These approaches should not be used if duplicate ids need to be honored. In other words, because id values are being applied to the first level keys, php will automatically destroy any rows with duplicated ids -- because a single level of an array cannot contain duplicate keys.
One way to avoid potentially destroying data is to call array_rand() on the original indexes of the input array, then filter those unique indexes by 5 randomly selected indexes. (Demo)
echo implode(
',',
array_column(
array_intersect_key(
$test,
array_flip(array_rand($test, 5))
),
'id'
)
);
Finally, if you want 5 randomly selected, randomly ordered ids which may be selected more than once, then just make 5 iterated calls of array_rand(). (Demo)
for ($x = 0, $delim = ''; $x < 5; ++$x, $delim = ',') {
echo $delim . $test[array_rand($test)]['id'];
}
Or (Demo)
echo implode(
',',
array_map(
fn() => $test[array_rand($test)]['id'],
range(1, 5)
)
);
You can proceed like this (short example) :
<?php
$items = array(
array("id" => 43, "pets" =>2),
array("id" => 40, "pets" =>8),
array("id" => 24, "pets" =>0),
array("id" => 23, "pets" =>4),
);
$ids = $items[array_rand($items)]["id"].",".$items[array_rand($items)]["id"].",".$items[array_rand($items)]["id"];
echo $ids;
// Output Example : 24, 40, 23
?>
It will choose a random key from the main array ($items), example : 3, and output the "id" :
$items[3]["id"]
for this example.
Here is a demo : http://sandbox.onlinephpfunctions.com/code/32787091e341cdf8e172d96b065b14b3ca834846

How to get array key with less than values

I have 2 arrays:
$arr_1=array(200, 300, 200, 200);
$arr_2=array(
1 => array(70, 90, 70, 20),
2 => array(115, 150, 115, 35),
3 => array(205, 250, 195, 55),
4 => array(325, 420, 325, 95),
5 => array(545, 700, 545, 155)
);
Now I need some way to get array keys for arrays in $arr_1 where all their values are less than all values from $arr_2.
In the above example it must return key 1 AND key 2 from $arr_2 without using a foreach loop.
You can use array_filter to filter the elements (it preserves keys) and then pass the result to array_keys to receive an array of keys.
Also, your condition can be spelled this way: "return subarrays from $arr_2 where highest value is smaller than smallest value of $arr_1."
$arr_1=array(200, 300, 200, 200);
$arr_2=array(
1 => array(70, 90, 70, 20),
2 => array(115, 150, 115, 35),
3 => array(205, 250, 195, 55),
4 => array(325, 420, 325, 95),
5 => array(545, 700, 545, 155)
);
$filtered = array_filter($arr_2, function($value) use ($arr_1) {
return max($value) < min($arr_1);
});
$keys = array_keys($filtered);
var_dump($keys);
If you are only interested in comparing the subarrays against the lowest value in $arr_1, then best practices dictates that you declare that value before entering the array_filter(). This will spare the function having to call min() on each iteration. (Demo)
$arr_1=[200,300,200,200];
$arr_2=[
1=>[70,90,70,20],
2=>[115,150,115,35],
3=>[205,250,195,55],
4=>[325,420,325,95],
5=>[545,700,545,155]
];
$limit=min($arr_1); // cache this value, so that min() isn't called on each iteration in array_filter()
$qualifying_keys=array_keys(array_filter($arr_2,function($a)use($limit){return max($a)<$limit;}));
var_export($qualifying_keys);
/*
array(
0=>1,
1=>2,
)
*/

Calculation of possible Solutions inside an php Array: How to improve my Algorithm?

Preamble:
This question is not about to learn PHP, nor is it code that I plan to use in a productive environment. I just want so see and learn better ways to do this work, as I did in my approach. So please only correct my code or show me better, faster or shorter solutions for doing it. The problem itself has already been solved. Thank you!
The Problem:
Some days ago a user asked a question here on SO. His problem has got my attention, because I wanted to find a way to solve his needs.
He wanted to get all possible key combinations of an PHP array, where the sum of the values is 100, or as close as possible to 100. He had given us an example array, which I will use for my examples too:
$array = array(25, 30, 50, 15, 20, 30);
For example, one result should be [2, 4, 5], because 50 + 20 + 30 is 100.
$sum = $array[2] + $array[4] + $array[5]; // = 100
I think the basic idea should be clear. Now let's take a look at my work ...
My Approach:
So this problem has got my attention as a developer. At first, I thought it would be pretty simple. Just do some addition and check the result. But then I've noticed that there some points to keep in mind ...
There are plenty of combinations to test. For the example array, there would be up to 720 (6! = 1*2*3*4*5*6 = 720) possible permutations. To get all possible combinations, I wanted to get all possible permutations of the array first.
But that was only the half truth. Because there could be double values in the array (as in your example the 30), we could not get all possible permutations of the array values, we had to get all possible permutations of the array keys instead.
So I've used the pc_permut function of the php cookbook and modified it for my needs. It will return all possible permutations in an array of keys.
/**
* gets all possible permutations of $array
* #param array $array
* #param array $permutations
* #return array
*/
function permutations($array, $permutations = array()) {
if( !empty($array) ) {
$result = array();
for( $i = count($array) - 1; $i >= 0; --$i ) {
$newItems = $array;
$newPerms = $permutations;
list($values) = array_splice($newItems, $i, 1);
array_unshift($newPerms, $values);
$result = array_merge($result, permutations($newItems, $newPerms));
}
}
else {
$result = array($permutations);
}
return $result;
}
The result of this function is a multidimensional array, containing all permutations in an ordered key array.
Array (
[0] => Array (
[0] => 0
[1] => 1
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)
[1] => Array (
[0] => 1
[1] => 0
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)
[...
)
So, for now I got all permutations to work with. The calculation of the possible combinations is not so hard at all. I'll just loop through the permutation, increment the sum until they've reached 100 or above and return the key combination.
But I find out that I missed one thing. As I got all possible permutations, there are even some results doubled in the list. To explain, this two results are basically the same:
[2, 4, 5]; // 50 + 20 + 30 = 100
[4, 5, 2]; // 20 + 30 + 50 = 100
I've ended up sorting the keys after calculation and use them as index in the resulting array. So it would be sure, that every combination only exists once in the result. This is my combinations function:
/**
* gets all possible key combinations of $array with a sum below or equal $maxSum
* #param array $array
* #param integer $maxSum
* #return array
*/
function combinations($array, $maxSum) {
// get all permutations of the array keys
$permutations = permutations(array_keys($array));
$combinations = array();
// loop all permutations
foreach( $permutations as $keys ) {
// create a container for each permutation to store calculation
$current = array(
"sum" => 0,
"keys" => array()
);
// now loop through the permutation keys
foreach( $keys as $key ) {
// if the addition is still between or equal $maxSum
if( $current["sum"] + $array[$key] <= $maxSum ) {
// increment the sum and add key to result
$current["sum"] += $array[$key];
$current["keys"][] = $key;
}
}
// to be sure each combination only exists once in the result
// order the keys and use them as array index
sort($current["keys"]);
$combinations[join("", $current["keys"])] = $current;
}
// remove the created key-index from array when finished
return array_values($combinations);
}
The execution is simple straight forward:
$array = array(25, 30, 50, 15, 20, 30);
print_r(combinations($array, 100));
The result is an array, containing all combinations. For our example array there are eleven possible combinations. The result looks like this:
Array (
[0] => Array (
[sum] => 90
[keys] => Array (
[0] => 0
[1] => 1
[2] => 3
[3] => 4
)
)
[1] => Array (
[sum] => 90
[keys] => Array (
[0] => 0
[1] => 2
[2] => 3
)
)
[...
Since I've written this script as an answer of the original question, I'll ask myself, if there would be another, even better way to do the work. Maybe there is a way without permutations, or a way to exclude same combinations from calculation or the resulting array. I know that I could execute the calculation directly in the permutations function too, but this would be basically the same work flow.
I would really like to get some advises, tips or improvements from you. I think here is some potential to improve the script, but I have actually no idea how to. But I'm sure it could be done more simple and straight forward ...
Thanks for your time! :)
Sorting the array leads to some possibilities: here is the one i'm thinking of:
I denote a(selectedIndexes) the element composed of all elements of selectedIndexes e.g.
a({25, 30, 30}) = (25, 30, 30)
P(n) is the set of all combinations of the indexes 1 to n and for clarity sake my arrays starts at index 1 (thus P(2)={1, 2, (1, 2)})
I'm using 2 break conditions explained in the pseudocode below. 1st one is the first element of aSorted = the allowed sum.
2nd one is the sum is too small compared to aSorted first element
selectedIndexes = {}
sum = 100
aSorted = {15, 20, 25, 30, 30, 50} //starting values for the example
//to clarify the following function
aSum = {15, 35, 60, 90}
function n(aSorted, sum, selectedIndexes){
compute aSum //precisely search in aSum for the index at which
//the elements are bigger than sum, and cut
answer = (P(count(aSum))) X a(selectedIndexes) // with X being the cartesian product
for (i=count(aSum)+1; i<=count(aSorted); i++){
newASorted = splice(aSorted, count(aSum))
// 1st break condition
if(newASorted is empty) return answer
// 2nd break condition the new sum < the first element of aSorted
if (aSorted(i)<sum && sum-aSorted(i)>=aSorted(1)){
answer += n(newASorted, sum-aSorted(i), push(selectedIndexes,
i))
}
}
return answer
}
The complexity of this algorithm feels quadratic(after a quick check it's more like of order n^log2(n)) in regard to the number of elements in the array
To make it less abstract, let's develop the example(warning i trust the example more than the pseudocode although i didn't see inaccuracies myself in the pseudocode):
n({15, 20, 25, 30, 30, 50}, 100, {}) = P(4) + n({15, 20, 25, 30, 30}, 50, {6}) + n({15, 20, 25, 30}, 70, {5})
Starting by developing the first n function of the right side of the equation
n({15, 20, 25, 30, 30}, 50, {5}) = (P(2) X {6}) + n({15, 20, 25, 30}, 20, {5, 6}) + n({15, 20, 25}, 20, {4, 6}) + n({15, 20}, 25, {3, 6})
n({15, 20, 25, 30}, 20, {5, 6}) = (P(1) X {(5, 6)}) // + n({15}, 0, {2, 5, 6}) (but 0<15) break condition 2
n({15, 20, 25}, 20, {4, 6}) = P(1) X {(4, 6)} // and break condition 2
n({15, 20}, 25, {3, 6}) = P(1) X {(3, 6)} // + n({15}, 5, {2, 3, 6}) (but 5<15) break condition 2
Now developing the second n function of the right side of the equation
n({15, 20, 25, 30}, 70, {5}) = (P(3) X {5}) + n({15, 20, 25}, 40, {4, 5})
n({15, 20, 25}, 40, {4, 5}) = (P(2) X {(4, 5)}) + n({15, 20}, 15, {3, 4, 5})
n({15, 20}, 15, {3, 4, 5}) = P(1) x {(3, 4, 5)} // + n({}, 0, {1, 3, 4, 5}) new break condition aSum is empty

How many times can x unique values be pulled from an array

I have an array of values (non unique) and I need to work out how many times I can pull out x (e.g 3) unique items from that array.
e.g.
[5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5]
What is the largest quantity of unique items of length 3 (e.g. [5, 4, 1]) I can retrieve?
For context, this is for an offer system in a shopping cart. The array of values is the product ids, and I need to know how many times I can apply a specific offer that requires 3 different items from the array of ids in order to be valid.
Thanks for any help - just ask if anything is unclear and I'll try to explain. If I've missed an existing question that answers this please let me know and I'll close this question.
Here's one way you can follow:
$basket = array(5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5);
$length = 3; //unique set size
function getSetCount($basket, $length) {
$counts = array_count_values($basket); //count of each ID
$totalCount = count($basket); //count of all elements
//maximum amount of sets
$max = floor($totalCount / $length);
//since every ID can be only once in a set, it all above $max occurences won't be able to fit any set.
$reducedCounts = array_map(function ($el) use ($max) {
return min($el, $max);
}, $counts);
//final result is "all elements that should fit in sets / size of set
return floor(array_sum($reducedCounts) / $length);
}
If you would like to print them:
function printSets($basket, $length) {
$counts = array_count_values($basket); //count of each ID
while(!empty($counts)) {
arsort($counts);//reverse sort with keeping the keys
$set = array_slice(array_keys($counts),0,$length); //get the set
echo implode(',', $set) . '<br/>';
foreach($set as $id) { //reduce counts
$counts[$id]--;
}
$counts = array_filter($counts); //clean zeros
}
}
The code above may not handle proparly some edge cases. But this is the idea.
Basically array_count_values() counts values' occurences and returns an array of value => count pairs. Then its easy to manipulate this data.
If i understand you correctly:
function getChunksCount($products)
{
// get unique ids
$uniqueProducts = array_unique($products);
// count unique ids
$count = count($uniqueProducts);
// let's get the number of chucks available
$chunkSize = 3;
// round to the floor
return floor($count / $chunkSize);
}
No complex logic or processing at all. Next time try to write down what exactly needs to be done in what order, and the solution might become pretty obvious :)
You can do it using array_unique and array_slice.
$arr = [5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5];
$new_arr = array_slice(array_unique($arr), 0, 3);
print_r($new_arr); //Array ( [0] => 5 [1] => 4 [2] => 1 )
You can use array_count_values.
<?php `$array = array(5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5); $vals = array_count_values($array); echo 'No. of NON Duplicate Items: '.count($vals).'<br><br>'; print_r($vals);`?>
Output is -Array ( [5] => 4 [4] => 2 1 => 2 [2] => 2 [3] => 2 )

Get max value from each column of a 2-dimensional array

I have an array within an array, for example:
[
[0, 20, 5],
[5, 0, 15],
[5, 10, 0]
]
I need to get the max number in each column.
The max of [0 , 5 , 5] is 5, so that goes into the result array.
The max of [20, 0, 10] is 20, so that goes into the result array.
The max of [5, 15, 0] is 15, so that goes into the result array.
The final result array must contain [5, 20, 15].
First, the array has to be transposed (flip the rows and columns):
function array_transpose($arr) {
$map_args = array_merge(array(NULL), $arr);
return call_user_func_array('array_map', $map_args);
}
(taken from Is there better way to transpose a PHP 2D array? - read that question for an explanation of how it works)
Then, you can map the max function on the new array:
$maxes = array_map('max', array_transpose($arr));
Example: http://codepad.org/3gPExrhO
Output [I think this is what you meant instead of (5, 15, 20) because you said index 1 should be max of (20, 0, 10)]:
Array
(
[0] => 5
[1] => 20
[2] => 15
)
Without the splat operator, array_map() with max() will return the max value for each row. ([20, 15, 10])
With the splat operator to transpose the data structure, the max value for each column is returned.
Code: (Demo)
$array = [
[0, 20, 5],
[5, 0, 15],
[5, 10, 0]
];
var_export(
array_map('max', ...$array)
);
Output:
array (
0 => 5,
1 => 20,
2 => 15,
)

Categories